Merge branch 'main' of https://github.com/github/codeql into python-ruby/track-through-summaries-pm

This commit is contained in:
Rasmus Lerchedahl Petersen
2023-06-09 14:16:34 +02:00
882 changed files with 22203 additions and 9953 deletions

View File

@@ -1,3 +1,9 @@
## 0.6.2
### Minor Analysis Improvements
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
## 0.6.1
No user-facing changes.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -778,7 +778,7 @@ module API {
or
exists(TypeTracker t2 |
result = trackUseNode(src, t2).track(t2, t) and
not result instanceof DataFlowPrivate::SelfParameterNode
not result instanceof DataFlow::SelfParameterNode
)
}
@@ -800,7 +800,7 @@ module API {
or
exists(TypeBackTracker t2, DataFlow::LocalSourceNode mid |
mid = trackDefNode(rhs, t2) and
not mid instanceof DataFlowPrivate::SelfParameterNode and
not mid instanceof DataFlow::SelfParameterNode and
result = mid.backtrack(t2, t)
)
}

View File

@@ -78,6 +78,19 @@ module SqlExecution {
}
}
/**
* A data-flow node that performs SQL sanitization.
*/
class SqlSanitization extends DataFlow::Node instanceof SqlSanitization::Range { }
/** Provides a class for modeling new SQL sanitization APIs. */
module SqlSanitization {
/**
* A data-flow node that performs SQL sanitization.
*/
abstract class Range extends DataFlow::Node { }
}
/**
* A data-flow node that executes a regular expression.
*
@@ -687,9 +700,7 @@ module Http {
* Gets a node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
deprecated DataFlow::Node getURL() {
result = super.getURL() or result = Request::Range.super.getAUrlPart()
}
deprecated DataFlow::Node getURL() { result = Request::Range.super.getAUrlPart() }
/**
* Holds if this request is made using a mode that disables SSL/TLS
@@ -715,14 +726,6 @@ module Http {
/** Gets a node which returns the body of the response */
abstract DataFlow::Node getResponseBody();
/**
* DEPRECATED: overwrite `getAUrlPart` instead.
*
* Gets a node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
deprecated DataFlow::Node getURL() { none() }
/**
* DEPRECATED: override `disablesCertificateValidation/2` instead.
*

View File

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

View File

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

View File

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

View File

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

View File

@@ -113,13 +113,6 @@ class ExprCfgNode extends AstCfgNode {
/** Gets the underlying expression. */
Expr getExpr() { result = e }
/**
* DEPRECATED: Use `getConstantValue` instead.
*
* Gets the textual (constant) value of this expression, if any.
*/
deprecated string getValueText() { result = this.getConstantValue().toString() }
/** Gets the constant value of this expression, if any. */
ConstantValue getConstantValue() { result = getConstantValue(this) }
}

View File

@@ -8,18 +8,21 @@ private import FlowSummaryImpl as FlowSummaryImpl
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.SSA
private import codeql.util.Boolean
private import codeql.util.Unit
/**
* A `LocalSourceNode` for a `self` variable. This is either an implicit `self`
* parameter or an implicit SSA entry definition.
* A `LocalSourceNode` for a `self` variable. This is the implicit `self`
* parameter, when it exists, otherwise the implicit SSA entry definition.
*/
private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
private SelfVariable self;
SelfLocalSourceNode() {
self = this.(SelfParameterNode).getSelfVariable()
self = this.(SelfParameterNodeImpl).getSelfVariable()
or
self = this.(SsaSelfDefinitionNode).getVariable()
self = this.(SsaSelfDefinitionNode).getVariable() and
not LocalFlow::localFlowSsaParamInput(_, this)
}
/** Gets the `self` variable. */
@@ -470,35 +473,28 @@ private module Cached {
import Cached
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) {
t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr())
or
exists(TypeTracker t2, StepSummary summary |
result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary)
)
private predicate isNotSelf(DataFlow::Node n) { not n instanceof SelfParameterNodeImpl }
private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig {
class State = Module;
predicate start(DataFlow::Node start, Module m) {
m = resolveConstantReadAccess(start.asExpr().getExpr())
}
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
/**
* We exclude steps into `self` parameters, and instead rely on the type of the
* enclosing module.
*/
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) {
StepSummary::step(trackModuleAccess(m, t), result, summary) and
not result instanceof SelfParameterNode
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
result = trackModuleAccess(m, TypeTracker::end())
}
predicate trackModuleAccess = CallGraphConstruction::Simple::Make<TrackModuleInput>::track/1;
pragma[nomagic]
private predicate hasUserDefinedNew(Module m) {
exists(DataFlow::MethodNode method |
// not `getAnAncestor` because singleton methods cannot be included
singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode())
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode())
)
}
@@ -531,141 +527,162 @@ private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
)
}
/** Holds if `n` is an instance of type `tp`. */
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
n.asExpr().getExpr() instanceof NilLiteral and
tp = TResolved("NilClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
tp = TResolved("FalseClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
tp = TResolved("TrueClass") and
exact = true
or
n.asExpr().getExpr() instanceof IntegerLiteral and
tp = TResolved("Integer") and
exact = true
or
n.asExpr().getExpr() instanceof FloatLiteral and
tp = TResolved("Float") and
exact = true
or
n.asExpr().getExpr() instanceof RationalLiteral and
tp = TResolved("Rational") and
exact = true
or
n.asExpr().getExpr() instanceof ComplexLiteral and
tp = TResolved("Complex") and
exact = true
or
n.asExpr().getExpr() instanceof StringlikeLiteral and
tp = TResolved("String") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
tp = TResolved("Array") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
tp = TResolved("Hash") and
exact = true
or
n.asExpr().getExpr() instanceof MethodBase and
tp = TResolved("Symbol") and
exact = true
or
n.asParameter() instanceof BlockParameter and
tp = TResolved("Proc") and
exact = true
or
n.asExpr().getExpr() instanceof Lambda and
tp = TResolved("Proc") and
exact = true
or
isStandardNewCall(n.asExpr(), tp, exact)
or
// `self` reference in method or top-level (but not in module or singleton method,
// where instance methods cannot be called; only singleton methods)
n =
any(SelfLocalSourceNode self |
exists(MethodBase m |
selfInMethod(self.getVariable(), m, tp) and
not m instanceof SingletonMethod and
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
)
or
selfInToplevel(self.getVariable(), tp) and
exact = true
)
or
// `in C => c then c.foo`
asModulePattern(n, tp) and
exact = false
or
// `case object when C then object.foo`
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
exact = false
}
pragma[nomagic]
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
t.start() and
(
isInstance(result, tp, exact)
or
exists(Module m |
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
exact = true
|
// needed for e.g. `C.new`
m = resolveConstantReadAccess(result.asExpr().getExpr())
or
// needed for e.g. `self.include`
selfInModule(result.(SelfLocalSourceNode).getVariable(), m)
or
// needed for e.g. `self.puts`
selfInMethod(result.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
)
)
or
exists(TypeTracker t2, StepSummary summary |
result = trackInstanceRec(tp, t2, exact, summary) and t = t2.append(summary)
)
}
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
localFlowStepTypeTracker(nodeFrom, nodeTo) and
summary.toString() = "level"
}
pragma[nomagic]
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
}
/**
* We exclude steps into `self` parameters and type checked variables. For those,
* we instead rely on the type of the enclosing module resp. the type being checked
* against, and apply an open-world assumption when determining possible dispatch
* targets.
*/
pragma[nomagic]
private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) {
exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) |
StepSummary::smallstep(mid, result, summary) and
not result instanceof SelfParameterNode
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
pragma[nomagic]
private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) {
n.asExpr().getExpr() instanceof NilLiteral and
tp = TResolved("NilClass") and
exact = true
or
localFlowStep(mid, result, summary) and
not hasAdjacentTypeCheckedReads(result)
)
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
tp = TResolved("FalseClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
tp = TResolved("TrueClass") and
exact = true
or
n.asExpr().getExpr() instanceof IntegerLiteral and
tp = TResolved("Integer") and
exact = true
or
n.asExpr().getExpr() instanceof FloatLiteral and
tp = TResolved("Float") and
exact = true
or
n.asExpr().getExpr() instanceof RationalLiteral and
tp = TResolved("Rational") and
exact = true
or
n.asExpr().getExpr() instanceof ComplexLiteral and
tp = TResolved("Complex") and
exact = true
or
n.asExpr().getExpr() instanceof StringlikeLiteral and
tp = TResolved("String") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
tp = TResolved("Array") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
tp = TResolved("Hash") and
exact = true
or
n.asExpr().getExpr() instanceof MethodBase and
tp = TResolved("Symbol") and
exact = true
or
n.asParameter() instanceof BlockParameter and
tp = TResolved("Proc") and
exact = true
or
n.asExpr().getExpr() instanceof Lambda and
tp = TResolved("Proc") and
exact = true
or
// `self` reference in method or top-level (but not in module or singleton method,
// where instance methods cannot be called; only singleton methods)
n =
any(SelfLocalSourceNode self |
exists(MethodBase m |
selfInMethod(self.getVariable(), m, tp) and
not m instanceof SingletonMethod and
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
)
or
selfInToplevel(self.getVariable(), tp) and
exact = true
)
or
// `in C => c then c.foo`
asModulePattern(n, tp) and
exact = false
or
// `case object when C then object.foo`
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
exact = false
}
pragma[nomagic]
private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) {
isStandardNewCall(n.asExpr(), tp, exact)
}
/** Holds if `n` is an instance of type `tp`. */
pragma[inline]
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
isInstanceNoCall(n, tp, exact)
or
isInstanceCall(n, tp, exact)
}
pragma[nomagic]
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
}
newtype State = additional MkState(Module m, Boolean exact)
predicate start(DataFlow::Node start, State state) {
exists(Module tp, boolean exact | state = MkState(tp, exact) |
isInstance(start, tp, exact)
or
exists(Module m |
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
exact = true
|
// needed for e.g. `C.new`
m = resolveConstantReadAccess(start.asExpr().getExpr())
or
// needed for e.g. `self.include`
selfInModule(start.(SelfLocalSourceNode).getVariable(), m)
or
// needed for e.g. `self.puts`
selfInMethod(start.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
)
)
}
pragma[nomagic]
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
// We exclude steps into `self` parameters. For those, we instead rely on the type of
// the enclosing module
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) and
isNotSelf(nodeTo)
or
// We exclude steps into type checked variables. For those, we instead rely on the
// type being checked against
localFlowStep(nodeFrom, nodeTo, summary) and
not hasAdjacentTypeCheckedReads(nodeTo)
}
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
}
class StateProj = Unit;
Unit stateProj(State state) { exists(state) and exists(result) }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n, Unit u) {
n instanceof SelfParameterNodeImpl and
exists(u)
}
}
pragma[nomagic]
private DataFlow::Node trackInstance(Module tp, boolean exact) {
result = trackInstance(tp, exact, TypeTracker::end())
result =
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(tp, exact))
}
pragma[nomagic]
@@ -706,30 +723,17 @@ private CfgScope getTargetInstance(RelevantCall call, string method) {
)
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
t.start() and result.asExpr().getExpr() = block
or
exists(TypeTracker t2, StepSummary summary |
result = trackBlockRec(block, t2, summary) and
t = t2.append(summary)
)
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
class State = Block;
predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
/**
* We exclude steps into `self` parameters, which may happen when the code
* base contains implementations of `call`.
*/
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) {
StepSummary::step(trackBlock(block, t), result, summary) and
not result instanceof SelfParameterNode
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlock(Block block) {
result = trackBlock(block, TypeTracker::end())
}
private predicate trackBlock = CallGraphConstruction::Simple::Make<TrackBlockInput>::track/1;
/** Holds if `m` is a singleton method named `name`, defined on `object. */
private predicate singletonMethod(MethodBase m, string name, Expr object) {
@@ -896,92 +900,98 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
)
}
/**
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
*
* This is only used for tracking singleton methods, where we want to be able
* to handle cases like
*
* ```rb
* def add_singleton x
* def x.foo; end
* end
*
* y = add_singleton C.new
* y.foo
* ```
*
* and
*
* ```rb
* class C
* def add_singleton_to_self
* def self.foo; end
* end
* end
*
* y = C.new
* y.add_singleton_to_self
* y.foo
* ```
*/
pragma[nomagic]
private predicate paramReturnFlow(
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
) {
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
TypeTrackerSpecific::callStep(call, arg, p) and
nodeTo.getPreUpdateNode() = arg and
summary.toString() = "return" and
(
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruction::InputSig {
/**
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
*
* This is only used for tracking singleton methods, where we want to be able
* to handle cases like
*
* ```rb
* def add_singleton x
* def x.foo; end
* end
*
* y = add_singleton C.new
* y.foo
* ```
*
* and
*
* ```rb
* class C
* def add_singleton_to_self
* def self.foo; end
* end
* end
*
* y = C.new
* y.add_singleton_to_self
* y.foo
* ```
*/
pragma[nomagic]
private predicate paramReturnFlow(
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
) {
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
TypeTrackerSpecific::callStep(call, arg, p) and
nodeTo.getPreUpdateNode() = arg and
summary.toString() = "return" and
(
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
or
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
singletonMethodOnInstance(_, _, nodeFromPreExpr)
)
|
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
or
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
singletonMethodOnInstance(_, _, nodeFromPreExpr)
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
)
|
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
or
nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess()
)
}
}
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) {
t.start() and
singletonMethodOnInstance(method, name, result.asExpr().getExpr())
or
exists(TypeTracker t2, StepSummary summary |
result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and
t = t2.append(summary) and
// Stop flow at redefinitions.
//
// Example:
// ```rb
// def x.foo; end
// def x.foo; end
// x.foo # <- we want to resolve this call to the second definition only
// ```
not singletonMethodOnInstance(_, name, result.asExpr().getExpr())
)
}
class State = MethodBase;
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstanceRec(
MethodBase method, string name, TypeTracker t, StepSummary summary
) {
exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) |
StepSummary::smallstep(mid, result, summary)
predicate start(DataFlow::Node start, MethodBase method) {
singletonMethodOnInstance(method, _, start.asExpr().getExpr())
}
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary)
or
paramReturnFlow(mid, result, summary)
localFlowStep(nodeFrom, nodeTo, summary)
}
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
or
localFlowStep(mid, result, summary)
)
paramReturnFlow(nodeFrom, nodeTo, summary)
}
class StateProj extends string {
StateProj() { singletonMethodOnInstance(_, this, _) }
}
StateProj stateProj(MethodBase method) { singletonMethodOnInstance(method, result, _) }
// Stop flow at redefinitions.
//
// Example:
// ```rb
// def x.foo; end
// def x.foo; end
// x.foo # <- we want to resolve this call to the second definition only
// ```
predicate filter(DataFlow::Node n, StateProj name) {
singletonMethodOnInstance(_, name, n.asExpr().getExpr())
}
}
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) {
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
result = CallGraphConstruction::Make<TrackSingletonMethodOnInstanceInput>::track(method) and
singletonMethodOnInstance(method, name, _)
}
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */

View File

@@ -1135,8 +1135,8 @@ module Impl<FullStateConfigSig Config> {
DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow
);
bindingset[node, state, t, ap]
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap);
bindingset[node, state, t0, ap]
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t);
bindingset[typ, contentType]
predicate typecheckStore(Typ typ, DataFlowType contentType);
@@ -1199,17 +1199,21 @@ module Impl<FullStateConfigSig Config> {
NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, TypOption argT,
ApOption argAp, Typ t, Ap ap, ApApprox apa
) {
fwdFlow0(node, state, cc, summaryCtx, argT, argAp, t, ap, apa) and
PrevStage::revFlow(node, state, apa) and
filter(node, state, t, ap)
fwdFlow1(node, state, cc, summaryCtx, argT, argAp, _, t, ap, apa)
}
pragma[inline]
additional predicate fwdFlow(
private predicate fwdFlow1(
NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, TypOption argT,
ApOption argAp, Typ t, Ap ap
ApOption argAp, Typ t0, Typ t, Ap ap, ApApprox apa
) {
fwdFlow(node, state, cc, summaryCtx, argT, argAp, t, ap, _)
fwdFlow0(node, state, cc, summaryCtx, argT, argAp, t0, ap, apa) and
PrevStage::revFlow(node, state, apa) and
filter(node, state, t0, ap, t)
}
pragma[nomagic]
private predicate typeStrengthen(Typ t0, Ap ap, Typ t) {
fwdFlow1(_, _, _, _, _, _, t0, t, ap, _) and t0 != t
}
pragma[assume_small_delta]
@@ -1339,6 +1343,11 @@ module Impl<FullStateConfigSig Config> {
private predicate fwdFlowConsCand(Typ t2, Ap cons, Content c, Typ t1, Ap tail) {
fwdFlowStore(_, t1, tail, c, t2, _, _, _, _, _, _) and
cons = apCons(c, t1, tail)
or
exists(Typ t0 |
typeStrengthen(t0, cons, t2) and
fwdFlowConsCand(t0, cons, c, t1, tail)
)
}
pragma[nomagic]
@@ -1359,7 +1368,7 @@ module Impl<FullStateConfigSig Config> {
ParamNodeOption summaryCtx, TypOption argT, ApOption argAp
) {
exists(ApHeadContent apc |
fwdFlow(node1, state, cc, summaryCtx, argT, argAp, t, ap) and
fwdFlow(node1, state, cc, summaryCtx, argT, argAp, t, ap, _) and
apc = getHeadContent(ap) and
readStepCand0(node1, apc, c, node2)
)
@@ -1520,14 +1529,14 @@ module Impl<FullStateConfigSig Config> {
NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap
) {
revFlow0(node, state, returnCtx, returnAp, ap) and
fwdFlow(node, state, _, _, _, _, _, ap)
fwdFlow(node, state, _, _, _, _, _, ap, _)
}
pragma[nomagic]
private predicate revFlow0(
NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap
) {
fwdFlow(node, state, _, _, _, _, _, ap) and
fwdFlow(node, state, _, _, _, _, _, ap, _) and
sinkNode(node, state) and
(
if hasSinkCallCtx()
@@ -1780,13 +1789,13 @@ module Impl<FullStateConfigSig Config> {
boolean fwd, int nodes, int fields, int conscand, int states, int tuples
) {
fwd = true and
nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, _, _)) and
nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, _, _, _)) and
fields = count(Content f0 | fwdConsCand(f0, _, _)) and
conscand = count(Content f0, Typ t, Ap ap | fwdConsCand(f0, t, ap)) and
states = count(FlowState state | fwdFlow(_, state, _, _, _, _, _, _)) and
states = count(FlowState state | fwdFlow(_, state, _, _, _, _, _, _, _)) and
tuples =
count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, TypOption argT,
ApOption argAp, Typ t, Ap ap | fwdFlow(n, state, cc, summaryCtx, argT, argAp, t, ap))
ApOption argAp, Typ t, Ap ap | fwdFlow(n, state, cc, summaryCtx, argT, argAp, t, ap, _))
or
fwd = false and
nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and
@@ -1963,10 +1972,10 @@ module Impl<FullStateConfigSig Config> {
)
}
bindingset[node, state, t, ap]
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
bindingset[node, state, t0, ap]
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
PrevStage::revFlowState(state) and
exists(t) and
t0 = t and
exists(ap) and
not stateBarrier(node, state) and
(
@@ -2197,8 +2206,8 @@ module Impl<FullStateConfigSig Config> {
import BooleanCallContext
predicate localStep(
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
DataFlowType t, LocalCc lcc
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, Typ t,
LocalCc lcc
) {
localFlowBigStep(node1, state1, node2, state2, preservesValue, t, _) and
exists(lcc)
@@ -2218,10 +2227,16 @@ module Impl<FullStateConfigSig Config> {
)
}
bindingset[node, state, t, ap]
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
bindingset[node, state, t0, ap]
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
exists(state) and
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any()) and
// We can get away with not using type strengthening here, since we aren't
// going to use the tracked types in the construction of Stage 4 access
// paths. For Stage 4 and onwards, the tracked types must be consistent as
// the cons candidates including types are used to construct subsequent
// access path approximations.
t0 = t and
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t0) else any()) and
(
notExpectsContent(node)
or
@@ -2241,6 +2256,16 @@ module Impl<FullStateConfigSig Config> {
import MkStage<Stage2>::Stage<Stage3Param>
}
bindingset[node, t0]
private predicate strengthenType(NodeEx node, DataFlowType t0, DataFlowType t) {
if castingNodeEx(node)
then
exists(DataFlowType nt | nt = node.getDataFlowType() |
if typeStrongerThan(nt, t0) then t = nt else (compatibleTypes(nt, t0) and t = t0)
)
else t = t0
}
private module Stage4Param implements MkStage<Stage3>::StageParam {
private module PrevStage = Stage3;
@@ -2274,8 +2299,8 @@ module Impl<FullStateConfigSig Config> {
pragma[nomagic]
predicate localStep(
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
DataFlowType t, LocalCc lcc
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, Typ t,
LocalCc lcc
) {
localFlowBigStep(node1, state1, node2, state2, preservesValue, t, _) and
PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and
@@ -2333,11 +2358,11 @@ module Impl<FullStateConfigSig Config> {
)
}
bindingset[node, state, t, ap]
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
bindingset[node, state, t0, ap]
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
exists(state) and
not clear(node, ap) and
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any()) and
strengthenType(node, t0, t) and
(
notExpectsContent(node)
or
@@ -2365,7 +2390,7 @@ module Impl<FullStateConfigSig Config> {
exists(AccessPathFront apf |
Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and
Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, _, TAccessPathFrontSome(argApf), _,
apf)
apf, _)
)
}
@@ -2579,8 +2604,8 @@ module Impl<FullStateConfigSig Config> {
import LocalCallContext
predicate localStep(
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
DataFlowType t, LocalCc lcc
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, Typ t,
LocalCc lcc
) {
localFlowBigStep(node1, state1, node2, state2, preservesValue, t, lcc) and
PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and
@@ -2609,9 +2634,9 @@ module Impl<FullStateConfigSig Config> {
)
}
bindingset[node, state, t, ap]
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any()) and
bindingset[node, state, t0, ap]
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
strengthenType(node, t0, t) and
exists(state) and
exists(ap)
}
@@ -2632,7 +2657,7 @@ module Impl<FullStateConfigSig Config> {
Stage5::parameterMayFlowThrough(p, _) and
Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and
Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), _,
TAccessPathApproxSome(apa), _, apa0)
TAccessPathApproxSome(apa), _, apa0, _)
)
}
@@ -2649,7 +2674,7 @@ module Impl<FullStateConfigSig Config> {
TSummaryCtxSome(ParamNodeEx p, FlowState state, DataFlowType t, AccessPath ap) {
exists(AccessPathApprox apa | ap.getApprox() = apa |
Stage5::parameterMayFlowThrough(p, apa) and
Stage5::fwdFlow(p, state, _, _, _, _, t, apa) and
Stage5::fwdFlow(p, state, _, _, Option<DataFlowType>::some(t), _, _, apa, _) and
Stage5::revFlow(p, state, _)
)
}
@@ -2820,9 +2845,7 @@ module Impl<FullStateConfigSig Config> {
ap = TAccessPathNil()
or
// ... or a step from an existing PathNode to another node.
pathStep(_, node, state, cc, sc, t, ap) and
Stage5::revFlow(node, state, ap.getApprox()) and
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any())
pathStep(_, node, state, cc, sc, t, ap)
} or
TPathNodeSink(NodeEx node, FlowState state) {
exists(PathNodeMid sink |
@@ -3340,13 +3363,24 @@ module Impl<FullStateConfigSig Config> {
ap = mid.getAp()
}
private predicate pathStep(
PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, DataFlowType t,
AccessPath ap
) {
exists(DataFlowType t0 |
pathStep0(mid, node, state, cc, sc, t0, ap) and
Stage5::revFlow(node, state, ap.getApprox()) and
strengthenType(node, t0, t)
)
}
/**
* Holds if data may flow from `mid` to `node`. The last step in or out of
* a callable is recorded by `cc`.
*/
pragma[assume_small_delta]
pragma[nomagic]
private predicate pathStep(
private predicate pathStep0(
PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, DataFlowType t,
AccessPath ap
) {
@@ -3964,7 +3998,7 @@ module Impl<FullStateConfigSig Config> {
ap = TPartialNil() and
exists(explorationLimit())
or
partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, sc4, t, ap) and
partialPathStep(_, node, state, cc, sc1, sc2, sc3, sc4, t, ap) and
distSrc(node.getEnclosingCallable()) <= explorationLimit()
} or
TPartialPathNodeRev(
@@ -3990,11 +4024,20 @@ module Impl<FullStateConfigSig Config> {
}
pragma[nomagic]
private predicate partialPathNodeMk0(
NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t, PartialAccessPath ap
private predicate partialPathStep(
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
TSummaryCtx2 sc2, TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t, PartialAccessPath ap
) {
partialPathStep(_, node, state, cc, sc1, sc2, sc3, sc4, t, ap) and
partialPathStep1(mid, node, state, cc, sc1, sc2, sc3, sc4, _, t, ap)
}
pragma[nomagic]
private predicate partialPathStep1(
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
TSummaryCtx2 sc2, TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t0, DataFlowType t,
PartialAccessPath ap
) {
partialPathStep0(mid, node, state, cc, sc1, sc2, sc3, sc4, t0, ap) and
not fullBarrier(node) and
not stateBarrier(node, state) and
not clearsContentEx(node, ap.getHead()) and
@@ -4002,9 +4045,14 @@ module Impl<FullStateConfigSig Config> {
notExpectsContent(node) or
expectsContentEx(node, ap.getHead())
) and
if node.asNode() instanceof CastingNode
then compatibleTypes(node.getDataFlowType(), t)
else any()
strengthenType(node, t0, t)
}
pragma[nomagic]
private predicate partialPathTypeStrengthen(
DataFlowType t0, PartialAccessPath ap, DataFlowType t
) {
partialPathStep1(_, _, _, _, _, _, _, _, t0, t, ap) and t0 != t
}
/**
@@ -4183,7 +4231,8 @@ module Impl<FullStateConfigSig Config> {
}
}
private predicate partialPathStep(
pragma[nomagic]
private predicate partialPathStep0(
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
TSummaryCtx2 sc2, TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t, PartialAccessPath ap
) {
@@ -4309,6 +4358,11 @@ module Impl<FullStateConfigSig Config> {
DataFlowType t1, PartialAccessPath ap1, Content c, DataFlowType t2, PartialAccessPath ap2
) {
partialPathStoreStep(_, t1, ap1, c, _, t2, ap2)
or
exists(DataFlowType t0 |
partialPathTypeStrengthen(t0, ap2, t2) and
apConsFwd(t1, ap1, c, t0, ap2)
)
}
pragma[nomagic]

View File

@@ -131,10 +131,10 @@ module LocalFlow {
/**
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
*/
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
predicate localFlowSsaParamInput(Node nodeFrom, SsaDefinitionExtNode nodeTo) {
nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
or
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
}
/**
@@ -143,14 +143,13 @@ module LocalFlow {
*
* This is intended to recover from flow not currently recognised by ordinary capture flow.
*/
predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
exists(Ssa::CapturedEntryDefinition def, ParameterNodeImpl p |
(nodeFrom = p or LocalFlow::localFlowSsaParamInput(p, nodeFrom)) and
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
exists(Ssa::CapturedEntryDefinition def |
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
p.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
or
p.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
)
}
@@ -164,7 +163,7 @@ module LocalFlow {
/**
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
* SSA definition `def`.
* some SSA definition.
*/
private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) {
exists(SsaImpl::DefinitionExt def |
@@ -182,6 +181,8 @@ module LocalFlow {
// Flow into phi (read) SSA definition node from def
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
)
or
localFlowSsaParamInput(nodeFrom, nodeTo)
// TODO
// or
// // Flow into uncertain SSA definition
@@ -223,6 +224,13 @@ module LocalFlow {
op.getExpr() instanceof BinaryLogicalOperation and
nodeFrom.asExpr() = op.getAnOperand()
)
or
nodeTo.(ParameterNodeImpl).getParameter() =
any(NamedParameter p |
p.(OptionalParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
or
p.(KeywordParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
)
}
}
@@ -279,12 +287,6 @@ private module Cached {
newtype TNode =
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
TReturningNode(CfgNodes::ReturningCfgNode n) or
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
exists(ReturningNode ret |
ret.(NodeImpl).getCfgScope() = scope and
ret.getKind() = kind
)
} or
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
TNormalParameterNode(Parameter p) {
p instanceof SimpleParameter or
@@ -326,12 +328,6 @@ private module Cached {
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
TSynthHashSplatParameterNode or TSummaryParameterNode;
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
or
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
}
cached
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -346,12 +342,6 @@ private module Cached {
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
or
@@ -373,10 +363,6 @@ private module Cached {
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
// Simple flow through library code is included in the exposed local
@@ -386,19 +372,11 @@ private module Cached {
/**
* This is the local flow predicate that is used in type tracking.
*
* This needs to exclude `localFlowSsaParamInput` due to a performance trick
* in type tracking, where such steps are treated as call steps.
*/
cached
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
exists(NamedParameter p |
defaultValueFlow(p, nodeFrom) and
nodeTo = LocalFlow::getParameterDefNode(p)
)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
// Flow into phi node from read
@@ -440,12 +418,10 @@ private module Cached {
n instanceof ExprNode and
not reachedFromExprOrEntrySsaDef(n)
or
// Ensure all entry SSA definitions are local sources -- for parameters, this
// is needed by type tracking
entrySsaDefinition(n)
or
// Needed for flow out in type tracking
n instanceof SynthReturnNode
// Ensure all entry SSA definitions are local sources, except those that correspond
// to parameters (which are themselves local sources)
entrySsaDefinition(n) and
not LocalFlow::localFlowSsaParamInput(_, n)
or
// Needed for stores in type tracking
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
@@ -507,7 +483,7 @@ private module Cached {
*/
cached
predicate exprNodeReturnedFromCached(ExprNode e, Callable c) {
exists(ReturningNode r |
exists(ReturnNode r |
nodeGetEnclosingCallable(r).asCallable() = c and
(
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
@@ -542,8 +518,6 @@ predicate nodeIsHidden(Node n) {
or
n instanceof SummaryParameterNode
or
n instanceof SynthReturnNode
or
n instanceof SynthHashSplatParameterNode
or
n instanceof SynthHashSplatArgumentNode
@@ -658,10 +632,10 @@ private module ParameterNodes {
* The value of the `self` parameter at function entry, viewed as a node in a data
* flow graph.
*/
class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode {
class SelfParameterNodeImpl extends ParameterNodeImpl, TSelfParameterNode {
private MethodBase method;
SelfParameterNode() { this = TSelfParameterNode(method) }
SelfParameterNodeImpl() { this = TSelfParameterNode(method) }
final MethodBase getMethod() { result = method }
@@ -937,24 +911,26 @@ private class NewCall extends DataFlowCall {
NewCall() { this.asCall().getExpr().(MethodCall).getMethodName() = "new" }
}
/** A data-flow node that represents a value syntactically returned by a callable. */
abstract class ReturningNode extends Node {
/** Gets the kind of this return node. */
abstract ReturnKind getKind();
pragma[nomagic]
predicate hasKind(ReturnKind kind, CfgScope scope) {
kind = this.getKind() and
scope = this.(NodeImpl).getCfgScope()
}
}
/** A data-flow node that represents a value returned by a callable. */
abstract class ReturnNode extends Node {
/** Gets the kind of this return node. */
abstract ReturnKind getKind();
}
/** A data-flow node that represents a value returned by a callable. */
abstract class SourceReturnNode extends ReturnNode {
/** Gets the kind of this return node. */
abstract ReturnKind getKindSource(); // only exists to avoid spurious negative recursion
final override ReturnKind getKind() { result = this.getKindSource() }
pragma[nomagic]
predicate hasKind(ReturnKind kind, CfgScope scope) {
kind = this.getKindSource() and
scope = this.(NodeImpl).getCfgScope()
}
}
private module ReturnNodes {
private predicate isValid(CfgNodes::ReturningCfgNode node) {
exists(ReturningStmt stmt, Callable scope |
@@ -976,14 +952,14 @@ private module ReturnNodes {
* A data-flow node that represents an expression explicitly returned by
* a callable.
*/
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
class ExplicitReturnNode extends SourceReturnNode, ReturningStatementNode {
ExplicitReturnNode() {
isValid(n) and
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
n.getScope() instanceof Callable
}
override ReturnKind getKind() {
override ReturnKind getKindSource() {
if n.getNode() instanceof BreakStmt
then result instanceof BreakReturnKind
else
@@ -1012,10 +988,10 @@ private module ReturnNodes {
* a callable. An implicit return happens when an expression can be the
* last thing that is evaluated in the body of the callable.
*/
class ExprReturnNode extends ReturningNode, ExprNode {
class ExprReturnNode extends SourceReturnNode, ExprNode {
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
override ReturnKind getKind() {
override ReturnKind getKindSource() {
exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() |
if isUserDefinedNew(scope)
then result instanceof NewReturnKind
@@ -1040,7 +1016,7 @@ private module ReturnNodes {
* the implicit `self` reference in `@x` will return data stored in the field
* `x` out to the call `C.new`.
*/
class InitializeReturnNode extends ExprPostUpdateNode, ReturningNode {
class InitializeReturnNode extends ExprPostUpdateNode, ReturnNode {
InitializeReturnNode() {
exists(Method initialize |
this.getCfgScope() = initialize and
@@ -1053,32 +1029,6 @@ private module ReturnNodes {
override ReturnKind getKind() { result instanceof NewReturnKind }
}
/**
* A synthetic data-flow node for joining flow from different syntactic
* returns into a single node.
*
* This node only exists to avoid computing the product of a large fan-in
* with a large fan-out.
*/
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
private CfgScope scope;
private ReturnKind kind;
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
/** Gets a syntactic return node that flows into this synthetic node. */
pragma[nomagic]
ReturningNode getAnInput() { result.hasKind(kind, scope) }
override ReturnKind getKind() { result = kind }
override CfgScope getCfgScope() { result = scope }
override Location getLocationImpl() { result = scope.getLocation() }
override string toStringImpl() { result = "return " + kind + " in " + scope }
}
private class SummaryReturnNode extends SummaryNode, ReturnNode {
private ReturnKind rk;
@@ -1281,6 +1231,8 @@ class DataFlowType extends TDataFlowType {
string toString() { result = "" }
}
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
/** Gets the type of `n` used for type pruning. */
DataFlowType getNodeType(NodeImpl n) { result = TTodoDataFlowType() and exists(n) }
@@ -1339,9 +1291,6 @@ private import PostUpdateNodes
/** A node that performs a type cast. */
class CastNode extends Node {
CastNode() {
// ensure that actual return nodes are included in the path graph
this instanceof ReturningNode
or
// ensure that all variable assignments are included in the path graph
this.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
}

View File

@@ -195,10 +195,22 @@ class ParameterNode extends LocalSourceNode, TParameterNode instanceof Parameter
/** Gets the parameter corresponding to this node, if any. */
final Parameter getParameter() { result = super.getParameter() }
/** Gets the callable that this parameter belongs to. */
final Callable getCallable() { result = super.getCfgScope() }
/** Gets the name of the parameter, if any. */
final string getName() { result = this.getParameter().(NamedParameter).getName() }
}
/**
* The value of an implicit `self` parameter at function entry, viewed as a node in a data
* flow graph.
*/
class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
/** Gets the underlying `self` variable. */
final SelfVariable getSelfVariable() { result = super.getSelfVariable() }
}
/**
* A data-flow node that is a source of local flow.
*/
@@ -328,9 +340,6 @@ private module Cached {
exists(Node mid | hasLocalSource(mid, source) |
localFlowStepTypeTracker(mid, sink)
or
// Explicitly include the SSA param input step as type-tracking omits this step.
LocalFlow::localFlowSsaParamInput(mid, sink)
or
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
)
}
@@ -1176,19 +1185,15 @@ class CallableNode extends StmtSequenceNode {
result = this.getBlockParameter().getAMethodCall("call")
}
/**
* Gets the canonical return node from this callable.
*
* Each callable has exactly one such node, and its location may not correspond
* to any particular return site - consider using `getAReturningNode` to get nodes
* whose locations correspond to return sites.
*/
Node getReturn() { result.(SynthReturnNode).getCfgScope() = callable }
/**
* Gets a data flow node whose value is about to be returned by this callable.
*/
Node getAReturningNode() { result = this.getReturn().(SynthReturnNode).getAnInput() }
Node getAReturnNode() { result.(ReturnNode).(NodeImpl).getCfgScope() = callable }
/**
* DEPRECATED. Use `getAReturnNode` instead.
*/
deprecated Node getAReturningNode() { result = this.getAReturnNode() }
}
/**

View File

@@ -166,28 +166,21 @@ module Public {
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
}
private predicate noComponentSpecific(SummaryComponent sc) {
not exists(getComponentSpecific(sc))
}
/** Gets a textual representation of this component used for flow summaries. */
private string getComponent(SummaryComponent sc) {
result = getComponentSpecific(sc)
or
noComponentSpecific(sc) and
(
exists(ArgumentPosition pos |
sc = TParameterSummaryComponent(pos) and
result = "Parameter[" + getArgumentPosition(pos) + "]"
)
or
exists(ParameterPosition pos |
sc = TArgumentSummaryComponent(pos) and
result = "Argument[" + getParameterPosition(pos) + "]"
)
or
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
exists(ArgumentPosition pos |
sc = TParameterSummaryComponent(pos) and
result = "Parameter[" + getArgumentPosition(pos) + "]"
)
or
exists(ParameterPosition pos |
sc = TArgumentSummaryComponent(pos) and
result = "Argument[" + getParameterPosition(pos) + "]"
)
or
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
}
/** Gets a textual representation of this stack used for flow summaries. */

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.dataflow.internal.DataFlowPrivate
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.Stdlib
private import codeql.ruby.frameworks.Core
@@ -319,19 +318,19 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
// A `self` reference that may resolve to an active record model object
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
SsaSelfDefinitionNode
DataFlow::SelfParameterNode
{
private ActiveRecordModelClass cls;
ActiveRecordModelClassSelfReference() {
exists(MethodBase m |
m = this.getCfgScope() and
m = this.getCallable() and
m.getEnclosingModule() = cls and
m = cls.getAMethod()
) and
// In a singleton method, `self` refers to the class itself rather than an
// instance of that class
not this.getSelfScope() instanceof SingletonMethod
not this.getSelfVariable().getDeclaringScope() instanceof SingletonMethod
}
final override ActiveRecordModelClass getClass() { result = cls }

View File

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

View File

@@ -21,7 +21,7 @@ module Rack {
AppCandidate() {
call = this.getInstanceMethod("call") and
call.getNumberOfParameters() = 1 and
call.getReturn() = trackRackResponse()
call.getAReturnNode() = trackRackResponse()
}
/**

View File

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

View File

@@ -77,4 +77,26 @@ module Sqlite3 {
override DataFlow::Node getSql() { result = this.getArgument(0) }
}
/**
* A call to `SQLite3::Database.quote`, considered as a sanitizer for SQL statements.
*/
private class SQLite3QuoteSanitization extends SqlSanitization {
SQLite3QuoteSanitization() {
this = API::getTopLevelMember("SQLite3").getMember("Database").getAMethodCall("quote")
}
}
/**
* Flow summary for `SQLite3::Database.quote()`.
*/
private class QuoteSummary extends SummarizedCallable {
QuoteSummary() { this = "SQLite3::Database.quote()" }
override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
/**
* Provides default sources, sinks and sanitizers for detecting SQL injection
@@ -53,4 +54,6 @@ module SqlInjection {
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
StringConstArrayInclusionCallBarrier
{ }
private class SqlSanitizationAsSanitizer extends Sanitizer, SqlSanitization { }
}

View File

@@ -58,6 +58,3 @@ module StoredXss {
import TaintTracking::Global<Config>
}
/** DEPRECATED: Alias for StoredXss */
deprecated module StoredXSS = StoredXss;

View File

@@ -243,9 +243,6 @@ private module Shared {
or
isFlowFromHelperMethod(node1, node2)
}
/** DEPRECATED: Alias for isAdditionalXssFlowStep */
deprecated predicate isAdditionalXSSFlowStep = isAdditionalXssFlowStep/2;
}
/**
@@ -275,9 +272,6 @@ module ReflectedXss {
*/
predicate isAdditionalXssTaintStep = Shared::isAdditionalXssFlowStep/2;
/** DEPRECATED: Alias for isAdditionalXssTaintStep */
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
/**
* A HTTP request input, considered as a flow source.
*/
@@ -286,15 +280,18 @@ module ReflectedXss {
}
}
/** DEPRECATED: Alias for ReflectedXss */
deprecated module ReflectedXSS = ReflectedXss;
private module OrmTracking {
/**
* A data flow configuration to track flow from finder calls to field accesses.
*/
private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof OrmInstantiation }
predicate isSource(DataFlow::Node source) {
// We currently only use ORM instances that come from a call site, so restrict the sources
// to calls. This works around a performance issue that would arise from using 'self' as a source
// in ActiveRecord models. Over time, library models should stop relying on OrmInstantiation and instead
// use API graphs or type-tracking the same way we track other types.
source instanceof OrmInstantiation and source instanceof DataFlow::CallNode
}
// Select any call receiver and narrow down later
predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode c).getReceiver() }
@@ -302,6 +299,8 @@ private module OrmTracking {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
Shared::isAdditionalXssFlowStep(node1, node2)
}
predicate isBarrierIn(DataFlow::Node node) { node instanceof DataFlow::SelfParameterNode }
}
import DataFlow::Global<Config>
@@ -330,9 +329,6 @@ module StoredXss {
*/
predicate isAdditionalXssTaintStep = Shared::isAdditionalXssFlowStep/2;
/** DEPRECATED: Alias for isAdditionalXssTaintStep */
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
private class OrmFieldAsSource extends Source instanceof DataFlow::CallNode {
OrmFieldAsSource() {
exists(DataFlow::CallNode subSrc |
@@ -346,6 +342,3 @@ module StoredXss {
private class FileSystemReadAccessAsSource extends Source instanceof FileSystemReadAccess { }
// TODO: Consider `FileNameSource` flowing to script tag `src` attributes and similar
}
/** DEPRECATED: Alias for StoredXss */
deprecated module StoredXSS = StoredXss;

View File

@@ -224,6 +224,50 @@ private module Cached {
private import Cached
private predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
stepNoCall(nodeFrom, nodeTo, summary)
or
stepCall(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
step(nodeFrom, _, summary)
}
bindingset[nodeFrom, t]
pragma[inline_late]
pragma[noopt]
private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
exists(StepSummary summary |
stepProj(nodeFrom, summary) and
result = t.append(summary) and
step(nodeFrom, nodeTo, summary)
)
}
private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
smallstepNoCall(nodeFrom, nodeTo, summary)
or
smallstepCall(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
smallstep(nodeFrom, _, summary)
}
bindingset[nodeFrom, t]
pragma[inline_late]
pragma[noopt]
private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
smallstepProj(nodeFrom, summary) and
result = t.append(summary) and
smallstep(nodeFrom, nodeTo, summary)
)
}
/**
* Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`.
*
@@ -298,21 +342,50 @@ class StepSummary extends TStepSummary {
module StepSummary {
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
* inter-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate is inlined, which enables better join-orders when
* the call graph construction and type tracking are mutually recursive.
* In such cases, non-linear recursion involving `step` will be limited
* to non-linear recursion for the parts of `step` that involve the
* call graph.
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate stepCall = Cached::stepCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* intra-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate stepNoCall = Cached::stepNoCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
pragma[inline]
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
stepNoCall(nodeFrom, nodeTo, summary)
or
stepCall(nodeFrom, nodeTo, summary)
}
/**
* Gets the summary that corresponds to having taken a forwards
* inter-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate smallstepNoCall = Cached::smallstepNoCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* intra-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate smallstepCall = Cached::smallstepCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
@@ -320,7 +393,6 @@ module StepSummary {
* Unlike `StepSummary::step`, this predicate does not compress
* type-preserving steps.
*/
pragma[inline]
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
smallstepNoCall(nodeFrom, nodeTo, summary)
or
@@ -431,10 +503,7 @@ class TypeTracker extends TTypeTracker {
*/
pragma[inline]
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
exists(StepSummary summary |
StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
result = this.append(pragma[only_bind_into](summary))
)
result = stepInlineLate(this, nodeFrom, nodeTo)
}
/**
@@ -463,10 +532,7 @@ class TypeTracker extends TTypeTracker {
*/
pragma[inline]
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
result = this.append(summary)
)
result = smallstepInlineLate(this, nodeFrom, nodeTo)
or
simpleLocalFlowStep(nodeFrom, nodeTo) and
result = this
@@ -481,6 +547,39 @@ module TypeTracker {
TypeTracker end() { result.end() }
}
pragma[nomagic]
private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) {
step(_, nodeTo, summary)
}
bindingset[nodeTo, t]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker backStepInlineLate(
TypeBackTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
) {
exists(StepSummary summary |
backStepProj(nodeTo, summary) and
result = t.prepend(summary) and
step(nodeFrom, nodeTo, summary)
)
}
private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) {
smallstep(_, nodeTo, summary)
}
bindingset[nodeTo, t]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker backSmallstepInlineLate(TypeBackTracker t, Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
backSmallstepProj(nodeTo, summary) and
result = t.prepend(summary) and
smallstep(nodeFrom, nodeTo, summary)
)
}
/**
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
*
@@ -564,10 +663,7 @@ class TypeBackTracker extends TTypeBackTracker {
*/
pragma[inline]
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
exists(StepSummary summary |
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
this = result.prepend(pragma[only_bind_into](summary))
)
this = backStepInlineLate(result, nodeFrom, nodeTo)
}
/**
@@ -596,10 +692,7 @@ class TypeBackTracker extends TTypeBackTracker {
*/
pragma[inline]
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
this = result.prepend(summary)
)
this = backSmallstepInlineLate(result, nodeFrom, nodeTo)
or
simpleLocalFlowStep(nodeFrom, nodeTo) and
this = result
@@ -635,3 +728,169 @@ module TypeBackTracker {
*/
TypeBackTracker end() { result.end() }
}
/**
* INTERNAL: Do not use.
*
* Provides logic for constructing a call graph in mutual recursion with type tracking.
*
* When type tracking is used to construct a call graph, we cannot use the join-order
* from `stepInlineLate`, because `step` becomes a recursive call, which means that we
* will have a conjunct with 3 recursive calls: the call to `step`, the call to `stepProj`,
* and the recursive type tracking call itself. The solution is to split the three-way
* non-linear recursion into two non-linear predicates: one that first joins with the
* projected `stepCall` relation, followed by a predicate that joins with the full
* `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the
* same way as in `stepInlineLate`).
*/
module CallGraphConstruction {
/** The input to call graph construction. */
signature module InputSig {
/** A state to track during type tracking. */
class State;
/** Holds if type tracking should start at `start` in state `state`. */
predicate start(Node start, State state);
/**
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
* which _does not_ depend on the call graph.
*
* Implementing this predicate using `StepSummary::[small]stepNoCall` yields
* standard type tracking.
*/
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary);
/**
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
* which _does_ depend on the call graph.
*
* Implementing this predicate using `StepSummary::[small]stepCall` yields
* standard type tracking.
*/
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary);
/** A projection of an element from the state space. */
class StateProj;
/** Gets the projection of `state`. */
StateProj stateProj(State state);
/** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */
predicate filter(Node n, StateProj stateProj);
}
/** Provides the `track` predicate for use in call graph construction. */
module Make<InputSig Input> {
pragma[nomagic]
private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
Input::stepNoCall(nodeFrom, _, summary)
}
pragma[nomagic]
private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
Input::stepCall(nodeFrom, _, summary)
}
bindingset[nodeFrom, t]
pragma[inline_late]
pragma[noopt]
private TypeTracker stepNoCallInlineLate(
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
) {
exists(StepSummary summary |
stepNoCallProj(nodeFrom, summary) and
result = t.append(summary) and
Input::stepNoCall(nodeFrom, nodeTo, summary)
)
}
bindingset[state]
pragma[inline_late]
private Input::StateProj stateProjInlineLate(Input::State state) {
result = Input::stateProj(state)
}
pragma[nomagic]
private Node track(Input::State state, TypeTracker t) {
t.start() and Input::start(result, state)
or
exists(Input::StateProj stateProj |
stateProj = stateProjInlineLate(state) and
not Input::filter(result, stateProj)
|
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
or
exists(StepSummary summary |
// non-linear recursion
Input::stepCall(trackCall(state, t, summary), result, summary)
)
)
}
bindingset[t, summary]
pragma[inline_late]
private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
result = t.append(summary)
}
pragma[nomagic]
private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
exists(TypeTracker t2 |
// non-linear recursion
result = track(state, t2) and
stepCallProj(result, summary) and
t = appendInlineLate(t2, summary)
)
}
/** Gets a node that can be reached from _some_ start node in state `state`. */
pragma[nomagic]
Node track(Input::State state) { result = track(state, TypeTracker::end()) }
}
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
module Simple {
/** The input to call graph construction. */
signature module InputSig {
/** A state to track during type tracking. */
class State;
/** Holds if type tracking should start at `start` in state `state`. */
predicate start(Node start, State state);
/** Holds if type tracking should stop at `n`. */
predicate filter(Node n);
}
/** Provides the `track` predicate for use in call graph construction. */
module Make<InputSig Input> {
private module I implements CallGraphConstruction::InputSig {
private import codeql.util.Unit
class State = Input::State;
predicate start(Node start, State state) { Input::start(start, state) }
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
}
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
StepSummary::stepCall(nodeFrom, nodeTo, summary)
}
class StateProj = Unit;
Unit stateProj(State state) { exists(state) and exists(result) }
predicate filter(Node n, Unit u) {
Input::filter(n) and
exists(u)
}
}
import CallGraphConstruction::Make<I>
}
}
}

View File

@@ -79,7 +79,7 @@ predicate jumpStep = DataFlowPrivate::jumpStep/2;
/** Holds if there is direct flow from `param` to a return. */
pragma[nomagic]
private predicate flowThrough(DataFlowPublic::ParameterNode param) {
exists(DataFlowPrivate::ReturningNode returnNode, DataFlowDispatch::ReturnKind rk |
exists(DataFlowPrivate::SourceReturnNode returnNode, DataFlowDispatch::ReturnKind rk |
param.flowsTo(returnNode) and
returnNode.hasKind(rk, param.(DataFlowPrivate::NodeImpl).getCfgScope())
|
@@ -213,15 +213,7 @@ predicate callStep(ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::Param
* recursion (or, at best, terrible performance), since identifying calls to library
* methods is done using API graphs (which uses type tracking).
*/
predicate callStep(Node nodeFrom, Node nodeTo) {
callStep(_, nodeFrom, nodeTo)
or
// In normal data-flow, this will be a local flow step. But for type tracking
// we model it as a call step, in order to avoid computing a potential
// self-cross product of all calls to a function that returns one of its parameters
// (only to later filter that flow out using `TypeTracker::append`).
DataFlowPrivate::LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
}
predicate callStep(Node nodeFrom, Node nodeTo) { callStep(_, nodeFrom, nodeTo) }
/**
* Holds if `nodeFrom` steps to `nodeTo` by being returned from a call.
@@ -233,19 +225,13 @@ predicate callStep(Node nodeFrom, Node nodeTo) {
predicate returnStep(Node nodeFrom, Node nodeTo) {
exists(ExprNodes::CallCfgNode call |
nodeFrom instanceof DataFlowPrivate::ReturnNode and
not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode and
nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and
// deliberately do not include `getInitializeTarget`, since calls to `new` should not
// get the return value from `initialize`. Any fields being set in the initializer
// will reach all reads via `callStep` and `localFieldStep`.
nodeTo.asExpr().getNode() = call.getNode()
)
or
// In normal data-flow, this will be a local flow step. But for type tracking
// we model it as a returning flow step, in order to avoid computing a potential
// self-cross product of all calls to a function that returns one of its parameters
// (only to later filter that flow out using `TypeTracker::append`).
nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom and
not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode
}
/**
@@ -443,28 +429,19 @@ module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
}
Node parameterOf(Node callable, SummaryComponent param) {
exists(
DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos,
DataFlowPrivate::ParameterNodeImpl p
|
exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos |
param = SummaryComponent::parameter(apos) and
DataFlowDispatch::parameterMatch(ppos, apos) and
p.isSourceParameterOf(callable.asExpr().getExpr(), ppos) and
// We need to include both `p` and the SSA definition for `p`, since in type-tracking
// the step from `p` to the SSA definition is considered a call step.
result =
[p.(DataFlow::Node), DataFlowPrivate::LocalFlow::getParameterDefNode(p.getParameter())]
result
.(DataFlowPrivate::ParameterNodeImpl)
.isSourceParameterOf(callable.asExpr().getExpr(), ppos)
)
}
Node returnOf(Node callable, SummaryComponent return) {
exists(DataFlowPrivate::SynthReturnNode ret |
return = SummaryComponent::return() and
ret.getCfgScope() = callable.asExpr().getExpr() and
// We need to include both `ret` and `ret.getAnInput()`, since in type-tracking
// the step from `ret.getAnInput()` to `ret` is considered a return step.
result = [ret.(DataFlow::Node), ret.getAnInput()]
)
return = SummaryComponent::return() and
result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() =
callable.asExpr().getExpr()
}
// Relating callables to nodes

View File

@@ -0,0 +1,22 @@
/**
* @name Print CFG
* @description Produces a representation of a file's Control Flow Graph.
* This query is used by the VS Code extension.
* @id rb/print-cfg
* @kind graph
* @tags ide-contextual-queries/print-cfg
*/
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::TestOutput
private import codeql.IDEContextual
/**
* Gets the source file to generate a CFG from.
*/
external string selectedSourceFile();
class MyRelevantNode extends RelevantNode {
MyRelevantNode() {
this.getScope().getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
}
}

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-all
version: 0.6.2-dev
version: 0.6.3-dev
groups: ruby
extractor: ruby
dbscheme: ruby.dbscheme

View File

@@ -1,3 +1,7 @@
## 0.6.2
No user-facing changes.
## 0.6.1
No user-facing changes.

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-queries
version: 0.6.2-dev
version: 0.6.3-dev
groups:
- ruby
- queries

View File

@@ -4,8 +4,7 @@
<example>
<p>Consider this regular expression:</p>
<sample language="ruby">
/^_(__|.)+_$/
</sample>
/^_(__|.)+_$/</sample>
<p>
Its sub-expression <code>"(__|.)+?"</code> can match the string
<code>"__"</code> either by the first alternative <code>"__"</code> to the
@@ -21,8 +20,7 @@
repetition:
</p>
<sample language="ruby">
/^_(__|[^_])+_$/
</sample>
/^_(__|[^_])+_$/</sample>
</example>
<include src="ReDoSReferences.inc.qhelp"/>
</qhelp>

View File

@@ -11,7 +11,7 @@
* select sink, source, sink, "$@", source, source.toString()
* ```
*
* To declare expecations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
* To declare expectations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
* Example of the corresponding test file, e.g. test.rb
* ```rb
* s = source(1)

View File

@@ -2,7 +2,7 @@ import codeql.ruby.AST
import codeql.ruby.dataflow.internal.DataFlowPrivate
import codeql.ruby.dataflow.internal.DataFlowDispatch
query predicate ret(ReturningNode node) { any() }
query predicate ret(SourceReturnNode node) { any() }
query predicate arg(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
n.argumentOf(call, pos) and

View File

@@ -2814,7 +2814,11 @@
| file://:0:0:0:0 | parameter position 0 of File.realdirpath | file://:0:0:0:0 | [summary] to write: return (return) in File.realdirpath |
| file://:0:0:0:0 | parameter position 0 of File.realpath | file://:0:0:0:0 | [summary] to write: return (return) in File.realpath |
| 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 Mysql2::Client.escape() | file://:0:0:0:0 | [summary] to write: return (return) in Mysql2::Client.escape() |
| file://:0:0:0:0 | parameter position 0 of Mysql2::Client.new() | file://:0:0:0:0 | [summary] to write: return (return) in Mysql2::Client.new() |
| file://:0:0:0:0 | parameter position 0 of PG.new() | file://:0:0:0:0 | [summary] to write: return (return) in PG.new() |
| file://:0:0:0:0 | parameter position 0 of SQLite3::Database.quote() | file://:0:0:0:0 | [summary] to write: return (return) in SQLite3::Database.quote() |
| file://:0:0:0:0 | parameter position 0 of Sequel.connect | file://:0:0:0:0 | [summary] to write: return (return) in Sequel.connect |
| 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 1.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |

View File

@@ -1,129 +1,72 @@
track
| type_tracker.rb:1:1:10:3 | self (Container) | type tracker without call steps | type_tracker.rb:1:1:10:3 | self (Container) |
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:18:1:21:3 | self (positional) |
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:18:1:21:3 | self in positional |
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:25:1:28:3 | self in keyword |
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker without call steps | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) |
| type_tracker.rb:2:5:5:7 | &block | type tracker without call steps | type_tracker.rb:2:5:5:7 | &block |
| type_tracker.rb:2:5:5:7 | field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | field= |
| type_tracker.rb:2:5:5:7 | return return in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:5:5:7 | return return in field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:5:5:7 | self (field=) | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:2:5:5:7 | self (field=) | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:2:5:5:7 | self (field=) | type tracker without call steps | type_tracker.rb:2:5:5:7 | self (field=) |
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:2:5:5:7 | self (field=) |
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:2:5:5:7 | self in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | self in field= |
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:3:9:3:23 | call to puts | type tracker without call steps | type_tracker.rb:3:9:3:23 | call to puts |
| type_tracker.rb:3:14:3:23 | call to field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:4:9:4:14 | @field | type tracker without call steps | type_tracker.rb:4:9:4:14 | @field |
| type_tracker.rb:7:5:9:7 | &block | type tracker without call steps | type_tracker.rb:7:5:9:7 | &block |
| type_tracker.rb:7:5:9:7 | field | type tracker without call steps | type_tracker.rb:7:5:9:7 | field |
| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:7:5:9:7 | self (field) | type tracker without call steps | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:7:5:9:7 | self in field | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:7:5:9:7 | self in field | type tracker without call steps | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:12:1:16:3 | &block | type tracker without call steps | type_tracker.rb:12:1:16:3 | &block |
| type_tracker.rb:12:1:16:3 | m | type tracker without call steps | type_tracker.rb:12:1:16:3 | m |
| type_tracker.rb:12:1:16:3 | return return in m | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m |
| type_tracker.rb:12:1:16:3 | self (m) | type tracker without call steps | type_tracker.rb:12:1:16:3 | self (m) |
| type_tracker.rb:12:1:16:3 | self in m | type tracker with call steps | type_tracker.rb:12:1:16:3 | self (m) |
| type_tracker.rb:12:1:16:3 | self in m | type tracker without call steps | type_tracker.rb:12:1:16:3 | self in m |
| type_tracker.rb:13:5:13:7 | var | type tracker without call steps | type_tracker.rb:13:5:13:7 | var |
| type_tracker.rb:13:11:13:19 | Container | type tracker without call steps | type_tracker.rb:13:11:13:19 | Container |
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:2:5:5:7 | self (field=) |
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:2:5:5:7 | self in field= |
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:13:11:13:23 | call to new | type tracker without call steps | type_tracker.rb:13:11:13:23 | call to new |
| type_tracker.rb:14:5:14:7 | [post] var | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:5:14:7 | [post] var | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:5:14:7 | [post] var | type tracker without call steps | type_tracker.rb:14:5:14:7 | [post] var |
| type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:17:14:23 | "hello" |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content attribute field | type_tracker.rb:14:5:14:7 | [post] var |
| type_tracker.rb:14:17:14:23 | __synth__0 | type tracker without call steps | type_tracker.rb:14:17:14:23 | __synth__0 |
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m |
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:15:5:15:18 | call to puts |
| type_tracker.rb:15:10:15:18 | call to field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:18:1:21:3 | &block | type tracker without call steps | type_tracker.rb:18:1:21:3 | &block |
| type_tracker.rb:18:1:21:3 | positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | positional |
| type_tracker.rb:18:1:21:3 | return return in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | return return in positional |
| type_tracker.rb:18:1:21:3 | return return in positional | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional |
| type_tracker.rb:18:1:21:3 | self (positional) | type tracker without call steps | type_tracker.rb:18:1:21:3 | self (positional) |
| type_tracker.rb:18:1:21:3 | self in positional | type tracker with call steps | type_tracker.rb:18:1:21:3 | self (positional) |
| type_tracker.rb:18:1:21:3 | self in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | self in positional |
| type_tracker.rb:18:16:18:17 | p1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:20:18:21 | p2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:19:5:19:11 | call to puts | type tracker without call steps | type_tracker.rb:19:5:19:11 | call to puts |
| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:18:1:21:3 | return return in positional |
| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:20:5:20:11 | call to puts |
| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional |
| type_tracker.rb:23:1:23:16 | call to positional | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional |
| type_tracker.rb:23:12:23:12 | 1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:23:12:23:12 | 1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:23:12:23:12 | 1 | type tracker without call steps | type_tracker.rb:23:12:23:12 | 1 |
| type_tracker.rb:23:15:23:15 | 2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:23:15:23:15 | 2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:23:15:23:15 | 2 | type tracker without call steps | type_tracker.rb:23:15:23:15 | 2 |
| type_tracker.rb:25:1:28:3 | &block | type tracker without call steps | type_tracker.rb:25:1:28:3 | &block |
| type_tracker.rb:25:1:28:3 | **kwargs | type tracker without call steps | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:25:1:28:3 | keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | return return in keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:30:1:30:21 | call to keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:32:1:32:27 | call to keyword |
| type_tracker.rb:25:1:28:3 | self (keyword) | type tracker without call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
| type_tracker.rb:25:1:28:3 | self in keyword | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
| type_tracker.rb:25:1:28:3 | self in keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | self in keyword |
| type_tracker.rb:25:13:25:14 | p1 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:18:25:19 | p2 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:26:5:26:11 | call to puts | type tracker without call steps | type_tracker.rb:26:5:26:11 | call to puts |
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:25:1:28:3 | return return in keyword |
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:27:5:27:11 | call to puts |
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:30:1:30:21 | call to keyword |
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword |
@@ -134,14 +77,12 @@ track
| type_tracker.rb:30:9:30:10 | :p1 | type tracker without call steps | type_tracker.rb:30:9:30:10 | :p1 |
| type_tracker.rb:30:9:30:13 | Pair | type tracker without call steps | type_tracker.rb:30:9:30:13 | Pair |
| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:30:13:30:13 | 3 | type tracker without call steps | type_tracker.rb:30:13:30:13 | 3 |
| type_tracker.rb:30:13:30:13 | 3 | type tracker without call steps with content element :p1 | type_tracker.rb:30:1:30:21 | ** |
| type_tracker.rb:30:16:30:17 | :p2 | type tracker without call steps | type_tracker.rb:30:16:30:17 | :p2 |
| type_tracker.rb:30:16:30:20 | Pair | type tracker without call steps | type_tracker.rb:30:16:30:20 | Pair |
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps | type_tracker.rb:30:20:30:20 | 4 |
| type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps with content element :p2 | type_tracker.rb:30:1:30:21 | ** |
@@ -151,14 +92,12 @@ track
| type_tracker.rb:31:9:31:10 | :p2 | type tracker without call steps | type_tracker.rb:31:9:31:10 | :p2 |
| type_tracker.rb:31:9:31:13 | Pair | type tracker without call steps | type_tracker.rb:31:9:31:13 | Pair |
| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:31:13:31:13 | 5 | type tracker without call steps | type_tracker.rb:31:13:31:13 | 5 |
| type_tracker.rb:31:13:31:13 | 5 | type tracker without call steps with content element :p2 | type_tracker.rb:31:1:31:21 | ** |
| type_tracker.rb:31:16:31:17 | :p1 | type tracker without call steps | type_tracker.rb:31:16:31:17 | :p1 |
| type_tracker.rb:31:16:31:20 | Pair | type tracker without call steps | type_tracker.rb:31:16:31:20 | Pair |
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps | type_tracker.rb:31:20:31:20 | 6 |
| type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps with content element :p1 | type_tracker.rb:31:1:31:21 | ** |
@@ -168,68 +107,33 @@ track
| type_tracker.rb:32:9:32:11 | :p2 | type tracker without call steps | type_tracker.rb:32:9:32:11 | :p2 |
| type_tracker.rb:32:9:32:16 | Pair | type tracker without call steps | type_tracker.rb:32:9:32:16 | Pair |
| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:32:16:32:16 | 7 | type tracker without call steps | type_tracker.rb:32:16:32:16 | 7 |
| type_tracker.rb:32:16:32:16 | 7 | type tracker without call steps with content element :p2 | type_tracker.rb:32:1:32:27 | ** |
| type_tracker.rb:32:19:32:21 | :p1 | type tracker without call steps | type_tracker.rb:32:19:32:21 | :p1 |
| type_tracker.rb:32:19:32:26 | Pair | type tracker without call steps | type_tracker.rb:32:19:32:26 | Pair |
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps | type_tracker.rb:32:26:32:26 | 8 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps with content element :p1 | type_tracker.rb:32:1:32:27 | ** |
| type_tracker.rb:34:1:53:3 | &block | type tracker without call steps | type_tracker.rb:34:1:53:3 | &block |
| type_tracker.rb:34:1:53:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:1:53:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | self in throughArray |
| type_tracker.rb:34:1:53:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:34:23:34:23 | y | type tracker with call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:26:34:26 | z | type tracker with call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:35:5:35:7 | tmp | type tracker without call steps | type_tracker.rb:35:5:35:7 | tmp |
@@ -315,46 +219,33 @@ track
| type_tracker.rb:50:5:50:10 | array4 | type tracker without call steps | type_tracker.rb:50:5:50:10 | array4 |
| type_tracker.rb:50:14:50:26 | Array | type tracker without call steps | type_tracker.rb:50:14:50:26 | Array |
| type_tracker.rb:50:14:50:26 | call to [] | type tracker without call steps | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:50:15:50:15 | 1 |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:50:17:50:17 | 2 |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:50:19:50:19 | 3 |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:50:21:50:21 | 4 |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:50:23:50:23 | 5 |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:50:25:50:25 | 6 |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:51:5:51:10 | [post] array4 | type tracker without call steps | type_tracker.rb:51:5:51:10 | [post] array4 |
| type_tracker.rb:51:5:51:13 | call to []= | type tracker without call steps | type_tracker.rb:51:5:51:13 | call to []= |
| type_tracker.rb:51:17:51:19 | __synth__0 | type tracker without call steps | type_tracker.rb:51:17:51:19 | __synth__0 |
| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
trackEnd
| type_tracker.rb:1:1:10:3 | self (Container) | type_tracker.rb:1:1:10:3 | self (Container) |
@@ -373,15 +264,6 @@ trackEnd
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type_tracker.rb:32:1:32:27 | self |
| type_tracker.rb:2:5:5:7 | &block | type_tracker.rb:2:5:5:7 | &block |
| type_tracker.rb:2:5:5:7 | field= | type_tracker.rb:2:5:5:7 | field= |
| type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:2:5:5:7 | self (field=) |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:9:3:23 | self |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:14:3:17 | self |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:4:9:4:14 | self |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:8:9:8:14 | self |
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self (field=) |
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self in field= |
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:3:9:3:23 | self |
@@ -390,25 +272,14 @@ trackEnd
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:8:9:8:14 | self |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:3:9:3:23 | call to puts | type_tracker.rb:3:9:3:23 | call to puts |
| type_tracker.rb:3:14:3:23 | call to field | type_tracker.rb:3:14:3:23 | call to field |
@@ -416,23 +287,14 @@ trackEnd
| type_tracker.rb:7:5:9:7 | &block | type_tracker.rb:7:5:9:7 | &block |
| type_tracker.rb:7:5:9:7 | field | type_tracker.rb:1:1:10:3 | Container |
| type_tracker.rb:7:5:9:7 | field | type_tracker.rb:7:5:9:7 | field |
| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:8:9:8:14 | self |
| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:8:9:8:14 | self |
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:12:1:16:3 | &block | type_tracker.rb:12:1:16:3 | &block |
| type_tracker.rb:12:1:16:3 | m | type_tracker.rb:12:1:16:3 | m |
| type_tracker.rb:12:1:16:3 | return return in m | type_tracker.rb:12:1:16:3 | return return in m |
| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:12:1:16:3 | self (m) |
| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:15:5:15:18 | self |
| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self (m) |
| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self in m |
| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:15:5:15:18 | self |
@@ -470,16 +332,10 @@ trackEnd
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:17:14:23 | __synth__0 |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:14:17:14:23 | __synth__0 | type_tracker.rb:14:17:14:23 | __synth__0 |
| type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:12:1:16:3 | return return in m |
| type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:15:5:15:18 | call to puts |
| type_tracker.rb:15:10:15:18 | call to field | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:18:1:21:3 | &block | type_tracker.rb:18:1:21:3 | &block |
| type_tracker.rb:18:1:21:3 | positional | type_tracker.rb:18:1:21:3 | positional |
| type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:18:1:21:3 | return return in positional |
| type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:23:1:23:16 | call to positional |
| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:18:1:21:3 | self (positional) |
| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:19:5:19:11 | self |
| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:20:5:20:11 | self |
| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self (positional) |
| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self in positional |
| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:19:5:19:11 | self |
@@ -487,17 +343,12 @@ trackEnd
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 |
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 |
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 |
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 |
| type_tracker.rb:19:5:19:11 | call to puts | type_tracker.rb:19:5:19:11 | call to puts |
| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:18:1:21:3 | return return in positional |
| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:20:5:20:11 | call to puts |
| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:23:1:23:16 | call to positional |
| type_tracker.rb:23:1:23:16 | call to positional | type_tracker.rb:23:1:23:16 | call to positional |
@@ -512,13 +363,6 @@ trackEnd
| type_tracker.rb:25:1:28:3 | &block | type_tracker.rb:25:1:28:3 | &block |
| type_tracker.rb:25:1:28:3 | **kwargs | type_tracker.rb:25:1:28:3 | **kwargs |
| type_tracker.rb:25:1:28:3 | keyword | type_tracker.rb:25:1:28:3 | keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:25:1:28:3 | return return in keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:30:1:30:21 | call to keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:31:1:31:21 | call to keyword |
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:32:1:32:27 | call to keyword |
| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:25:1:28:3 | self (keyword) |
| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:26:5:26:11 | self |
| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:27:5:27:11 | self |
| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self (keyword) |
| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self in keyword |
| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:26:5:26:11 | self |
@@ -526,17 +370,12 @@ trackEnd
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 |
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 |
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 |
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 |
| type_tracker.rb:26:5:26:11 | call to puts | type_tracker.rb:26:5:26:11 | call to puts |
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:25:1:28:3 | return return in keyword |
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:27:5:27:11 | call to puts |
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:30:1:30:21 | call to keyword |
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:31:1:31:21 | call to keyword |
@@ -587,80 +426,45 @@ trackEnd
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 |
| type_tracker.rb:34:1:53:3 | &block | type_tracker.rb:34:1:53:3 | &block |
| type_tracker.rb:34:1:53:3 | return return in throughArray | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:1:53:3 | self in throughArray | type_tracker.rb:34:1:53:3 | self in throughArray |
| type_tracker.rb:34:1:53:3 | throughArray | type_tracker.rb:34:1:53:3 | throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z |
| type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp |
| type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array |
@@ -743,29 +547,22 @@ trackEnd
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:51:5:51:10 | array4 |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:52:5:52:10 | array4 |
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:50:15:50:15 | 1 |
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:50:17:50:17 | 2 |
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:50:19:50:19 | 3 |
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:50:21:50:21 | 4 |
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:50:23:50:23 | 5 |
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:50:25:50:25 | 6 |
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:51:5:51:10 | [post] array4 |
| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:52:5:52:10 | array4 |
| type_tracker.rb:51:5:51:13 | call to []= | type_tracker.rb:51:5:51:13 | call to []= |
| type_tracker.rb:51:17:51:19 | __synth__0 | type_tracker.rb:51:17:51:19 | __synth__0 |
| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:52:5:52:13 | ...[...] |
forwardButNoBackwardFlow
backwardButNoForwardFlow

View File

@@ -36,8 +36,8 @@ actionDispatchRoutes
actionDispatchControllerMethods
| 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 | 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:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:39:5 | index |
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:41:3:42:5 | show |
| 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 | app/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 |
@@ -66,3 +66,17 @@ mimeTypeMatchRegExpInterpretations
| mime_type.rb:12:7:12:15 | "foo/bar" |
| mime_type.rb:13:11:13:11 | s |
| mime_type.rb:14:7:14:7 | s |
requestInputAccesses
| app/controllers/comments_controller.rb:3:5:3:18 | call to params |
| app/controllers/comments_controller.rb:4:5:4:22 | call to parameters |
| app/controllers/comments_controller.rb:5:5:5:15 | call to GET |
| app/controllers/comments_controller.rb:6:5:6:16 | call to POST |
| app/controllers/comments_controller.rb:7:5:7:28 | call to query_parameters |
| app/controllers/comments_controller.rb:8:5:8:30 | call to request_parameters |
| app/controllers/comments_controller.rb:9:5:9:31 | call to filtered_parameters |
| app/controllers/comments_controller.rb:38:5:38:14 | call to params |
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
| 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 |

View File

@@ -2,6 +2,7 @@ private import codeql.ruby.AST
private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Concepts
private import codeql.ruby.frameworks.data.ModelsAsData
private import codeql.ruby.DataFlow
private import codeql.ruby.Regexp as RE
@@ -36,3 +37,5 @@ query predicate mimeTypeMatchRegExpInterpretations(
) {
any()
}
query predicate requestInputAccesses(Http::Server::RequestInputAccess a) { any() }

View File

@@ -33,6 +33,9 @@ class CommentsController < ApplicationController
response.last_modified = Date.yesterday
response.weak_etag = "value"
response.strong_etag = "value"
req = ActionDispatch::Request.new(request.env)
req.params
end
def show

View File

@@ -0,0 +1,5 @@
| Mysql2.rb:10:16:10:48 | call to query | Mysql2.rb:10:27:10:47 | "SELECT * FROM users" |
| Mysql2.rb:13:16:13:73 | call to query | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." |
| Mysql2.rb:17:16:17:76 | call to query | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." |
| Mysql2.rb:21:16:21:57 | call to execute | Mysql2.rb:20:31:20:82 | "SELECT * FROM users WHERE id ..." |
| Mysql2.rb:25:16:25:60 | call to execute | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." |

View File

@@ -0,0 +1,5 @@
private import codeql.ruby.DataFlow
private import codeql.ruby.Concepts
private import codeql.ruby.frameworks.Mysql2
query predicate mysql2SqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }

View File

@@ -0,0 +1,30 @@
class UsersController < ActionController::Base
def mysql2_handler(event:, context:)
name = params[:user_name]
conn = Mysql2::Client.new(
host: "127.0.0.1",
username: "root"
)
# GOOD: SQL statement is not constructed from user input
results1 = conn.query("SELECT * FROM users")
# BAD: SQL statement constructed from user input
results2 = conn.query("SELECT * FROM users WHERE username='#{name}'")
# GOOD: user input is escaped
escaped = Mysql2::Client.escape(name)
results3 = conn.query("SELECT * FROM users WHERE username='#{escaped}'")
# GOOD: user input is escaped
statement1 = conn.prepare("SELECT * FROM users WHERE id >= ? AND username = ?")
results4 = statement1.execute(1, name, :as => :array)
# BAD: SQL statement constructed from user input
statement2 = conn.prepare("SELECT * FROM users WHERE username='#{name}' AND password = ?")
results4 = statement2.execute("password", :as => :array)
# NOT EXECUTED
statement3 = conn.prepare("SELECT * FROM users WHERE username = ?")
end
end

View File

@@ -0,0 +1,23 @@
sequelSqlConstruction
| sequel.rb:62:29:62:49 | call to cast | sequel.rb:62:45:62:48 | name |
| sequel.rb:65:29:65:49 | call to function | sequel.rb:65:45:65:48 | name |
sequelSqlExecution
| sequel.rb:9:9:9:60 | ...[...] | sequel.rb:9:14:9:59 | "SELECT * FROM users WHERE use..." |
| sequel.rb:12:9:12:64 | call to run | sequel.rb:12:18:12:63 | "SELECT * FROM users WHERE use..." |
| sequel.rb:15:9:17:11 | call to fetch | sequel.rb:15:20:15:65 | "SELECT * FROM users WHERE use..." |
| sequel.rb:20:9:20:65 | ...[...] | sequel.rb:20:14:20:64 | "SELECT * FROM users WHERE use..." |
| sequel.rb:23:9:23:65 | call to execute | sequel.rb:23:22:23:65 | "SELECT * FROM users WHERE use..." |
| sequel.rb:26:9:26:71 | call to execute_ddl | sequel.rb:26:26:26:71 | "SELECT * FROM users WHERE use..." |
| sequel.rb:29:9:29:71 | call to execute_dui | sequel.rb:29:26:29:71 | "SELECT * FROM users WHERE use..." |
| sequel.rb:32:9:32:74 | call to execute_insert | sequel.rb:32:29:32:74 | "SELECT * FROM users WHERE use..." |
| sequel.rb:35:9:35:62 | ... << ... | sequel.rb:35:17:35:62 | "SELECT * FROM users WHERE use..." |
| sequel.rb:38:9:38:79 | call to fetch_rows | sequel.rb:38:25:38:70 | "SELECT * FROM users WHERE use..." |
| sequel.rb:41:9:41:81 | call to with_sql_all | sequel.rb:41:35:41:80 | "SELECT * FROM users WHERE use..." |
| sequel.rb:44:9:44:84 | call to with_sql_delete | sequel.rb:44:38:44:83 | "SELECT * FROM users WHERE use..." |
| sequel.rb:47:9:47:90 | call to with_sql_each | sequel.rb:47:36:47:81 | "SELECT * FROM users WHERE use..." |
| sequel.rb:50:9:50:83 | call to with_sql_first | sequel.rb:50:37:50:82 | "SELECT * FROM users WHERE use..." |
| sequel.rb:53:9:53:84 | call to with_sql_insert | sequel.rb:53:38:53:83 | "SELECT * FROM users WHERE use..." |
| sequel.rb:56:9:56:90 | call to with_sql_single_value | sequel.rb:56:44:56:89 | "SELECT * FROM users WHERE use..." |
| sequel.rb:59:9:59:84 | call to with_sql_update | sequel.rb:59:38:59:83 | "SELECT * FROM users WHERE use..." |
| sequel.rb:62:9:62:20 | ...[...] | sequel.rb:62:14:62:19 | :table |
| sequel.rb:65:9:65:20 | ...[...] | sequel.rb:65:14:65:19 | :table |

View File

@@ -0,0 +1,7 @@
private import codeql.ruby.DataFlow
private import codeql.ruby.Concepts
private import codeql.ruby.frameworks.Sequel
query predicate sequelSqlConstruction(SqlConstruction c, DataFlow::Node sql) { sql = c.getSql() }
query predicate sequelSqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }

View File

@@ -0,0 +1,67 @@
require 'sequel'
class UsersController < ActionController::Base
def sequel_handler(event:, context:)
name = params[:name]
conn = Sequel.sqlite("sqlite://example.db")
# BAD: SQL statement constructed from user input
conn["SELECT * FROM users WHERE username='#{name}'"]
# BAD: SQL statement constructed from user input
conn.run("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn.fetch("SELECT * FROM users WHERE username='#{name}'") do |row|
puts row[:name]
end
# GOOD: SQL statement is not constructed from user input
conn["SELECT * FROM users WHERE username='im_not_input'"]
# BAD: SQL statement constructed from user input
conn.execute "SELECT * FROM users WHERE username=#{name}"
# BAD: SQL statement constructed from user input
conn.execute_ddl "SELECT * FROM users WHERE username='#{name}'"
# BAD: SQL statement constructed from user input
conn.execute_dui "SELECT * FROM users WHERE username='#{name}'"
# BAD: SQL statement constructed from user input
conn.execute_insert "SELECT * FROM users WHERE username='#{name}'"
# BAD: SQL statement constructed from user input
conn << "SELECT * FROM users WHERE username='#{name}'"
# BAD: SQL statement constructed from user input
conn.fetch_rows("SELECT * FROM users WHERE username='#{name}'"){|row| }
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_all("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_delete("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_each("SELECT * FROM users WHERE username='#{name}'"){|row| }
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_first("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_insert("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_single_value("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn.dataset.with_sql_update("SELECT * FROM users WHERE username='#{name}'")
# BAD: SQL statement constructed from user input
conn[:table].select(Sequel.cast(:a, name))
# BAD: SQL statement constructed from user input
conn[:table].select(Sequel.function(name))
end
end

View File

@@ -40,6 +40,9 @@ edges
| PolynomialReDoS.rb:70:12:70:24 | ...[...] | PolynomialReDoS.rb:70:5:70:8 | name |
| PolynomialReDoS.rb:73:32:73:35 | name | PolynomialReDoS.rb:76:35:76:39 | input |
| PolynomialReDoS.rb:76:35:76:39 | input | PolynomialReDoS.rb:77:5:77:9 | input |
| PolynomialReDoS.rb:103:5:103:8 | name | PolynomialReDoS.rb:105:5:105:8 | name |
| PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:103:12:103:24 | ...[...] |
| PolynomialReDoS.rb:103:12:103:24 | ...[...] | PolynomialReDoS.rb:103:5:103:8 | name |
| lib/index.rb:2:11:2:11 | x | lib/index.rb:4:13:4:13 | x |
| lib/index.rb:8:13:8:13 | x | lib/index.rb:9:15:9:15 | x |
| lib/index.rb:8:13:8:13 | x | lib/index.rb:11:16:11:16 | x |
@@ -91,6 +94,10 @@ nodes
| PolynomialReDoS.rb:73:32:73:35 | name | semmle.label | name |
| PolynomialReDoS.rb:76:35:76:39 | input | semmle.label | input |
| PolynomialReDoS.rb:77:5:77:9 | input | semmle.label | input |
| PolynomialReDoS.rb:103:5:103:8 | name | semmle.label | name |
| PolynomialReDoS.rb:103:12:103:17 | call to params | semmle.label | call to params |
| PolynomialReDoS.rb:103:12:103:24 | ...[...] | semmle.label | ...[...] |
| PolynomialReDoS.rb:105:5:105:8 | name | semmle.label | name |
| lib/index.rb:2:11:2:11 | x | semmle.label | x |
| lib/index.rb:4:13:4:13 | x | semmle.label | x |
| lib/index.rb:8:13:8:13 | x | semmle.label | x |
@@ -121,6 +128,8 @@ subpaths
| PolynomialReDoS.rb:62:5:62:22 | call to gsub | PolynomialReDoS.rb:54:12:54:17 | call to params | PolynomialReDoS.rb:62:5:62:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:56:31:56:33 | \\s+ | regular expression | PolynomialReDoS.rb:54:12:54:17 | call to params | user-provided value |
| PolynomialReDoS.rb:66:5:66:34 | call to match? | PolynomialReDoS.rb:54:12:54:17 | call to params | PolynomialReDoS.rb:66:5:66:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:58:30:58:32 | \\s+ | regular expression | PolynomialReDoS.rb:54:12:54:17 | call to params | user-provided value |
| PolynomialReDoS.rb:77:5:77:22 | call to gsub | PolynomialReDoS.rb:70:12:70:17 | call to params | PolynomialReDoS.rb:77:5:77:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:72:28:72:30 | \\s+ | regular expression | PolynomialReDoS.rb:70:12:70:17 | call to params | user-provided value |
| PolynomialReDoS.rb:105:5:105:23 | ... =~ ... | PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:105:5:105:8 | name | This $@ that depends on a $@ may run slow on strings starting with '''' and with many repetitions of ' '. | PolynomialReDoS.rb:100:397:100:399 | \\s* | regular expression | PolynomialReDoS.rb:103:12:103:17 | call to params | user-provided value |
| PolynomialReDoS.rb:105:5:105:23 | ... =~ ... | PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:105:5:105:8 | name | This $@ that depends on a $@ may run slow on strings starting with '''' and with many repetitions of ' '. | PolynomialReDoS.rb:100:405:100:407 | \\s* | regular expression | PolynomialReDoS.rb:103:12:103:17 | call to params | user-provided value |
| lib/index.rb:4:13:4:26 | call to match | lib/index.rb:2:11:2:11 | x | lib/index.rb:4:13:4:13 | x | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | lib/index.rb:4:22:4:23 | a+ | regular expression | lib/index.rb:2:11:2:11 | x | library input |
| lib/index.rb:9:15:9:28 | call to match | lib/index.rb:8:13:8:13 | x | lib/index.rb:9:15:9:15 | x | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | lib/index.rb:9:24:9:25 | a+ | regular expression | lib/index.rb:8:13:8:13 | x | library input |
| lib/index.rb:11:16:11:276 | call to match | lib/index.rb:8:13:8:13 | x | lib/index.rb:11:16:11:16 | x | This $@ that depends on a $@ may run slow on strings starting with 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'. | lib/index.rb:11:271:11:272 | .* | regular expression | lib/index.rb:8:13:8:13 | x | library input |

View File

@@ -76,4 +76,32 @@ class FooController < ActionController::Base
def re_compile_indirect_2 (reg, input)
input.gsub reg, '' # NOT GOOD
end
# See https://github.com/dependabot/dependabot-core/blob/37dc1767fde9b7184020763f4d0c1434f93d11d6/python/lib/dependabot/python/requirement_parser.rb#L6-L25
NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/
EXTRA = /[a-zA-Z0-9\-_\.]+/
COMPARISON = /===|==|>=|<=|<|>|~=|!=/
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/
HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|\\|$)/
REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
ENV_VAR =
/python_version|python_full_version|os_name|sys_platform|
platform_release|platform_system|platform_version|platform_machine|
platform_python_implementation|implementation_name|
implementation_version/
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
def use_marker_expr
name = params[:name] # source
name =~ MARKER_EXPR
end
end