Merge branch 'main' into jcogs33/shared-sink-kind-validation

This commit is contained in:
Jami
2023-06-14 08:06:34 -04:00
committed by GitHub
893 changed files with 16779 additions and 17965 deletions

View File

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

View File

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

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

@@ -700,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
@@ -728,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

@@ -34,4 +34,5 @@ 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.Yaml
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. */
@@ -150,12 +153,12 @@ class DataFlowCall extends TDataFlowCall {
*/
class SummaryCall extends DataFlowCall, TSummaryCall {
private FlowSummaryImpl::Public::SummarizedCallable c;
private DataFlow::Node receiver;
private FlowSummaryImpl::Private::SummaryNode receiver;
SummaryCall() { this = TSummaryCall(c, receiver) }
/** Gets the data flow node that this call targets. */
DataFlow::Node getReceiver() { result = receiver }
FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
@@ -374,7 +377,9 @@ private module Cached {
cached
newtype TDataFlowCall =
TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, DataFlow::Node receiver) {
TSummaryCall(
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
) {
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
}
@@ -470,35 +475,28 @@ private module Cached {
import Cached
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) {
t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr())
or
exists(TypeTracker t2, StepSummary summary |
result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary)
)
private predicate isNotSelf(DataFlow::Node n) { not n instanceof SelfParameterNodeImpl }
private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig {
class State = Module;
predicate start(DataFlow::Node start, Module m) {
m = resolveConstantReadAccess(start.asExpr().getExpr())
}
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
/**
* We exclude steps into `self` parameters, and instead rely on the type of the
* enclosing module.
*/
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) {
StepSummary::step(trackModuleAccess(m, t), result, summary) and
not result instanceof SelfParameterNode
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
result = trackModuleAccess(m, TypeTracker::end())
}
predicate trackModuleAccess = CallGraphConstruction::Simple::Make<TrackModuleInput>::track/1;
pragma[nomagic]
private predicate hasUserDefinedNew(Module m) {
exists(DataFlow::MethodNode method |
// not `getAnAncestor` because singleton methods cannot be included
singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode())
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode())
)
}
@@ -531,141 +529,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 +725,17 @@ private CfgScope getTargetInstance(RelevantCall call, string method) {
)
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
t.start() and result.asExpr().getExpr() = block
or
exists(TypeTracker t2, StepSummary summary |
result = trackBlockRec(block, t2, summary) and
t = t2.append(summary)
)
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
class State = Block;
predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
/**
* We exclude steps into `self` parameters, which may happen when the code
* base contains implementations of `call`.
*/
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) {
StepSummary::step(trackBlock(block, t), result, summary) and
not result instanceof SelfParameterNode
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlock(Block block) {
result = trackBlock(block, TypeTracker::end())
}
private predicate trackBlock = CallGraphConstruction::Simple::Make<TrackBlockInput>::track/1;
/** Holds if `m` is a singleton method named `name`, defined on `object. */
private predicate singletonMethod(MethodBase m, string name, Expr object) {
@@ -896,92 +902,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
@@ -307,30 +309,16 @@ private module Cached {
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
)
} or
TSummaryNode(
FlowSummaryImpl::Public::SummarizedCallable c,
FlowSummaryImpl::Private::SummaryNodeState state
) {
FlowSummaryImpl::Private::summaryNodeRange(c, state)
} or
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
} or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TSynthHashSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
or
c.getAnArgument() instanceof CfgNodes::ExprNodes::PairCfgNode
}
class TParameterNode =
class TSourceParameterNode =
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
TSynthHashSplatParameterNode or TSummaryParameterNode;
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
or
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
}
TSynthHashSplatParameterNode;
cached
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -346,12 +334,6 @@ private module Cached {
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
or
@@ -365,7 +347,8 @@ private module Cached {
exprFrom = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
}
/** This is the local flow predicate that is exposed. */
@@ -373,10 +356,6 @@ private module Cached {
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
// Simple flow through library code is included in the exposed local
@@ -386,19 +365,11 @@ private module Cached {
/**
* This is the local flow predicate that is used in type tracking.
*
* This needs to exclude `localFlowSsaParamInput` due to a performance trick
* in type tracking, where such steps are treated as call steps.
*/
cached
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
exists(NamedParameter p |
defaultValueFlow(p, nodeFrom) and
nodeTo = LocalFlow::getParameterDefNode(p)
)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
// Flow into phi node from read
@@ -434,18 +405,18 @@ private module Cached {
cached
predicate isLocalSourceNode(Node n) {
n instanceof TParameterNode
n instanceof TSourceParameterNode
or
n instanceof SummaryParameterNode
or
// Expressions that can't be reached from another entry definition or expression
n instanceof ExprNode and
not reachedFromExprOrEntrySsaDef(n)
or
// Ensure all entry SSA definitions are local sources -- for parameters, this
// is needed by type tracking
entrySsaDefinition(n)
or
// Needed for flow out in type tracking
n instanceof SynthReturnNode
// Ensure all entry SSA definitions are local sources, except those that correspond
// to parameters (which are themselves local sources)
entrySsaDefinition(n) and
not LocalFlow::localFlowSsaParamInput(_, n)
or
// Needed for stores in type tracking
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
@@ -507,7 +478,7 @@ private module Cached {
*/
cached
predicate exprNodeReturnedFromCached(ExprNode e, Callable c) {
exists(ReturningNode r |
exists(ReturnNode r |
nodeGetEnclosingCallable(r).asCallable() = c and
(
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
@@ -538,11 +509,7 @@ predicate nodeIsHidden(Node n) {
or
isDesugarNode(n.(ExprNode).getExprNode().getExpr())
or
n instanceof SummaryNode
or
n instanceof SummaryParameterNode
or
n instanceof SynthReturnNode
n instanceof FlowSummaryNode
or
n instanceof SynthHashSplatParameterNode
or
@@ -658,10 +625,10 @@ private module ParameterNodes {
* The value of the `self` parameter at function entry, viewed as a node in a data
* flow graph.
*/
class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode {
class SelfParameterNodeImpl extends ParameterNodeImpl, TSelfParameterNode {
private MethodBase method;
SelfParameterNode() { this = TSelfParameterNode(method) }
SelfParameterNodeImpl() { this = TSelfParameterNode(method) }
final MethodBase getMethod() { result = method }
@@ -783,47 +750,41 @@ private module ParameterNodes {
}
/** A parameter for a library callable with a flow summary. */
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
private FlowSummaryImpl::Public::SummarizedCallable sc;
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
private ParameterPosition pos_;
SummaryParameterNode() { this = TSummaryParameterNode(sc, pos_) }
SummaryParameterNode() {
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos_)
}
override Parameter getParameter() { none() }
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
sc = c.asLibraryCallable() and pos = pos_
this.getSummarizedCallable() = c.asLibraryCallable() and pos = pos_
}
override CfgScope getCfgScope() { none() }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
override EmptyLocation getLocationImpl() { any() }
override string toStringImpl() { result = "parameter " + pos_ + " of " + sc }
}
}
import ParameterNodes
/** A data-flow node used to model flow summaries. */
class SummaryNode extends NodeImpl, TSummaryNode {
FlowSummaryImpl::Public::SummarizedCallable c;
FlowSummaryImpl::Private::SummaryNodeState state;
SummaryNode() { this = TSummaryNode(c, state) }
class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
/** Gets the summarized callable that this node belongs to. */
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = c }
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
result = this.getSummaryNode().getSummarizedCallable()
}
override CfgScope getCfgScope() { none() }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
override DataFlowCallable getEnclosingCallable() {
result.asLibraryCallable() = this.getSummarizedCallable()
}
override EmptyLocation getLocationImpl() { any() }
override string toStringImpl() { result = "[summary] " + state + " in " + c }
override string toStringImpl() { result = this.getSummaryNode().toString() }
}
/** A data-flow node that represents a call argument. */
@@ -883,15 +844,20 @@ private module ArgumentNodes {
}
}
private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) }
private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
private DataFlowCall call_;
private ArgumentPosition pos_;
SummaryArgumentNode() {
FlowSummaryImpl::Private::summaryArgumentNode(call_, this.getSummaryNode(), pos_)
}
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
none()
}
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
call = call_ and pos = pos_
}
}
@@ -937,24 +903,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 +944,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 +980,10 @@ private module ReturnNodes {
* a callable. An implicit return happens when an expression can be the
* last thing that is evaluated in the body of the callable.
*/
class ExprReturnNode extends ReturningNode, ExprNode {
class ExprReturnNode extends SourceReturnNode, ExprNode {
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
override ReturnKind getKind() {
override ReturnKind getKindSource() {
exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() |
if isUserDefinedNew(scope)
then result instanceof NewReturnKind
@@ -1040,7 +1008,7 @@ private module ReturnNodes {
* the implicit `self` reference in `@x` will return data stored in the field
* `x` out to the call `C.new`.
*/
class InitializeReturnNode extends ExprPostUpdateNode, ReturningNode {
class InitializeReturnNode extends ExprPostUpdateNode, ReturnNode {
InitializeReturnNode() {
exists(Method initialize |
this.getCfgScope() = initialize and
@@ -1053,42 +1021,16 @@ private module ReturnNodes {
override ReturnKind getKind() { result instanceof NewReturnKind }
}
/**
* A synthetic data-flow node for joining flow from different syntactic
* returns into a single node.
*
* This node only exists to avoid computing the product of a large fan-in
* with a large fan-out.
*/
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
private CfgScope scope;
private ReturnKind kind;
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
/** Gets a syntactic return node that flows into this synthetic node. */
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 class SummaryReturnNode extends FlowSummaryNode, ReturnNode {
private ReturnKind rk;
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) }
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this.getSummaryNode(), rk) }
override ReturnKind getKind() {
result = rk
or
exists(NewCall new |
TLibraryCallable(c) = viableLibraryCallable(new) and
TLibraryCallable(this.getSummarizedCallable()) = viableLibraryCallable(new) and
result instanceof NewReturnKind
)
}
@@ -1121,12 +1063,15 @@ private module OutNodes {
}
}
private class SummaryOutNode extends SummaryNode, OutNode {
SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
private class SummaryOutNode extends FlowSummaryNode, OutNode {
private DataFlowCall call;
private ReturnKind kind_;
override DataFlowCall getCall(ReturnKind kind) {
FlowSummaryImpl::Private::summaryOutNode(result, this, kind)
SummaryOutNode() {
FlowSummaryImpl::Private::summaryOutNode(call, this.getSummaryNode(), kind_)
}
override DataFlowCall getCall(ReturnKind kind) { result = call and kind = kind_ }
}
}
@@ -1141,7 +1086,8 @@ predicate jumpStep(Node pred, Node succ) {
or
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
or
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred, succ)
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
succ.(FlowSummaryNode).getSummaryNode())
or
any(AdditionalJumpStep s).step(pred, succ)
}
@@ -1206,7 +1152,8 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
))
).getReceiver()
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
or
storeStepCommon(node1, c, node2)
}
@@ -1240,7 +1187,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
or
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
}
/**
@@ -1249,7 +1197,7 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
or
// Filter out keyword arguments that are part of the method signature from
// the hash-splat parameter
@@ -1270,7 +1218,7 @@ predicate clearsContent(Node n, ContentSet c) {
* at node `n`.
*/
predicate expectsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n, c)
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
}
private newtype TDataFlowType =
@@ -1281,6 +1229,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) }
@@ -1325,10 +1275,12 @@ private module PostUpdateNodes {
override string toStringImpl() { result = "[post] " + e.toString() }
}
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNodeImpl {
private Node pre;
private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl {
private FlowSummaryNode pre;
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
SummaryPostUpdateNode() {
FlowSummaryImpl::Private::summaryPostUpdateNode(this.getSummaryNode(), pre.getSummaryNode())
}
override Node getPreUpdateNode() { result = pre }
}
@@ -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
}
@@ -1406,7 +1355,7 @@ predicate lambdaSourceCall(CfgNodes::ExprNodes::CallCfgNode call, LambdaCallKind
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
lambdaSourceCall(call.asCall(), kind, receiver)
or
receiver = call.(SummaryCall).getReceiver() and
receiver.(FlowSummaryNode).getSummaryNode() = call.(SummaryCall).getReceiver() and
if receiver.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pos | pos.isBlock()))
then kind = TYieldCallKind()
else kind = TLambdaCallKind()

View File

@@ -191,14 +191,26 @@ class ExprNode extends Node, TExprNode {
* The value of a parameter at function entry, viewed as a node in a data
* flow graph.
*/
class ParameterNode extends LocalSourceNode, TParameterNode instanceof ParameterNodeImpl {
class ParameterNode extends LocalSourceNode instanceof ParameterNodeImpl {
/** Gets the parameter corresponding to this node, if any. */
final Parameter getParameter() { result = super.getParameter() }
/** Gets the callable that this parameter belongs to. */
final Callable getCallable() { result = super.getCfgScope() }
/** Gets the name of the parameter, if any. */
final string getName() { result = this.getParameter().(NamedParameter).getName() }
}
/**
* The value of an implicit `self` parameter at function entry, viewed as a node in a data
* flow graph.
*/
class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
/** Gets the underlying `self` variable. */
final SelfVariable getSelfVariable() { result = super.getSelfVariable() }
}
/**
* A data-flow node that is a source of local flow.
*/
@@ -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

@@ -180,6 +180,11 @@ module Public {
result = "Argument[" + getParameterPosition(pos) + "]"
)
or
exists(string synthetic |
sc = TSyntheticGlobalSummaryComponent(synthetic) and
result = "SyntheticGlobal[" + synthetic + "]"
)
or
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
}
@@ -505,6 +510,9 @@ module Private {
or
// Add the post-update node corresponding to the requested argument node
outputState(c, s) and isCallbackParameter(s)
or
// Add the parameter node for parameter side-effects
outputState(c, s) and s = SummaryComponentStack::argument(_)
}
private newtype TSummaryNodeState =
@@ -530,7 +538,7 @@ module Private {
* this state represents that the components in `s` _remain to be written_ to
* the output.
*/
class SummaryNodeState extends TSummaryNodeState {
private class SummaryNodeState extends TSummaryNodeState {
/** Holds if this state is a valid input state for `c`. */
pragma[nomagic]
predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
@@ -559,6 +567,42 @@ module Private {
}
}
private newtype TSummaryNode =
TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) {
summaryNodeRange(c, state)
} or
TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) {
summaryParameterNodeRange(c, pos)
}
abstract class SummaryNode extends TSummaryNode {
abstract string toString();
abstract SummarizedCallable getSummarizedCallable();
}
private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode {
private SummarizedCallable c;
private SummaryNodeState state;
SummaryInternalNode() { this = TSummaryInternalNode(c, state) }
override string toString() { result = "[summary] " + state + " in " + c }
override SummarizedCallable getSummarizedCallable() { result = c }
}
private class SummaryParamNode extends SummaryNode, TSummaryParameterNode {
private SummarizedCallable c;
private ParameterPosition pos;
SummaryParamNode() { this = TSummaryParameterNode(c, pos) }
override string toString() { result = "[summary param] " + pos + " in " + c }
override SummarizedCallable getSummarizedCallable() { result = c }
}
/**
* Holds if `state` represents having read from a parameter at position
* `pos` in `c`. In this case we are not synthesizing a data-flow node,
@@ -574,7 +618,7 @@ module Private {
* Holds if a synthesized summary node is needed for the state `state` in summarized
* callable `c`.
*/
predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
state.isInputState(c, _) and
not parameterReadState(c, state, _)
or
@@ -582,22 +626,22 @@ module Private {
}
pragma[noinline]
private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
private SummaryNode summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
exists(SummaryNodeState state | state.isInputState(c, s) |
result = summaryNode(c, state)
result = TSummaryInternalNode(c, state)
or
exists(ParameterPosition pos |
parameterReadState(c, state, pos) and
result.(ParamNode).isParameterOf(inject(c), pos)
result = TSummaryParameterNode(c, pos)
)
)
}
pragma[noinline]
private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
private SummaryNode summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
exists(SummaryNodeState state |
state.isOutputState(c, s) and
result = summaryNode(c, state)
result = TSummaryInternalNode(c, state)
)
}
@@ -605,12 +649,14 @@ module Private {
* Holds if a write targets `post`, which is a post-update node for a
* parameter at position `pos` in `c`.
*/
private predicate isParameterPostUpdate(Node post, SummarizedCallable c, ParameterPosition pos) {
private predicate isParameterPostUpdate(
SummaryNode post, SummarizedCallable c, ParameterPosition pos
) {
post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos))
}
/** Holds if a parameter node at position `pos` is required for `c`. */
predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) {
private predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) {
parameterReadState(c, _, pos)
or
// Same as `isParameterPostUpdate(_, c, pos)`, but can be used in a negative context
@@ -618,7 +664,7 @@ module Private {
}
private predicate callbackOutput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ReturnKind rk
) {
any(SummaryNodeState state).isInputState(c, s) and
s.head() = TReturnSummaryComponent(rk) and
@@ -626,7 +672,7 @@ module Private {
}
private predicate callbackInput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, ArgumentPosition pos
SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos
) {
any(SummaryNodeState state).isOutputState(c, s) and
s.head() = TParameterSummaryComponent(pos) and
@@ -634,7 +680,7 @@ module Private {
}
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) {
callbackOutput(c, _, receiver, _)
or
callbackInput(c, _, receiver, _)
@@ -647,10 +693,10 @@ module Private {
* `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
* `getCallbackReturnType()`.
*/
DataFlowType summaryNodeType(Node n) {
exists(Node pre |
DataFlowType summaryNodeType(SummaryNode n) {
exists(SummaryNode pre |
summaryPostUpdateNode(n, pre) and
result = getNodeType(pre)
result = summaryNodeType(pre)
)
or
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
@@ -662,12 +708,12 @@ module Private {
)
or
head = TWithoutContentSummaryComponent(_) and
result = getNodeType(summaryNodeInputState(c, s.tail()))
result = summaryNodeType(summaryNodeInputState(c, s.tail()))
or
exists(ReturnKind rk |
head = TReturnSummaryComponent(rk) and
result =
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
getCallbackReturnType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.tail())), rk)
)
or
@@ -675,6 +721,11 @@ module Private {
head = TSyntheticGlobalSummaryComponent(sg) and
result = getSyntheticGlobalType(sg)
)
or
exists(ParameterPosition pos |
head = TArgumentSummaryComponent(pos) and
result = getParameterType(c, pos)
)
)
or
n = summaryNodeOutputState(c, s) and
@@ -691,7 +742,7 @@ module Private {
or
exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) |
result =
getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
getCallbackParameterType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.tail())), pos)
)
or
@@ -703,9 +754,14 @@ module Private {
)
}
/** Holds if summary node `p` is a parameter with position `pos`. */
predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) {
p = TSummaryParameterNode(_, pos)
}
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
predicate summaryOutNode(DataFlowCall c, SummaryNode out, ReturnKind rk) {
exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver |
callbackOutput(callable, s, receiver, rk) and
out = summaryNodeInputState(callable, s) and
c = summaryDataFlowCall(receiver)
@@ -713,8 +769,8 @@ module Private {
}
/** Holds if summary node `arg` is at position `pos` in the call `c`. */
predicate summaryArgumentNode(DataFlowCall c, Node arg, ArgumentPosition pos) {
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
predicate summaryArgumentNode(DataFlowCall c, SummaryNode arg, ArgumentPosition pos) {
exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver |
callbackInput(callable, s, receiver, pos) and
arg = summaryNodeOutputState(callable, s) and
c = summaryDataFlowCall(receiver)
@@ -722,10 +778,10 @@ module Private {
}
/** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
predicate summaryPostUpdateNode(Node post, Node pre) {
predicate summaryPostUpdateNode(SummaryNode post, SummaryNode pre) {
exists(SummarizedCallable c, ParameterPosition pos |
isParameterPostUpdate(post, c, pos) and
pre.(ParamNode).isParameterOf(inject(c), pos)
pre = TSummaryParameterNode(c, pos)
)
or
exists(SummarizedCallable callable, SummaryComponentStack s |
@@ -736,7 +792,7 @@ module Private {
}
/** Holds if summary node `ret` is a return node of kind `rk`. */
predicate summaryReturnNode(Node ret, ReturnKind rk) {
predicate summaryReturnNode(SummaryNode ret, ReturnKind rk) {
exists(SummaryComponentStack s |
ret = summaryNodeOutputState(_, s) and
s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
@@ -748,7 +804,9 @@ module Private {
* node, and back out to `p`.
*/
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(inject(c), ppos) |
exists(SummarizedCallable c, ParameterPosition ppos |
p.isParameterOf(inject(c), pragma[only_bind_into](ppos))
|
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
summary(c, inputContents, outputContents, _) and
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and
@@ -763,7 +821,7 @@ module Private {
* Holds if there is a local step from `pred` to `succ`, which is synthesized
* from a flow summary.
*/
predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
predicate summaryLocalStep(SummaryNode pred, SummaryNode succ, boolean preservesValue) {
exists(
SummarizedCallable c, SummaryComponentStack inputContents,
SummaryComponentStack outputContents
@@ -789,7 +847,7 @@ module Private {
* Holds if there is a read step of content `c` from `pred` to `succ`, which
* is synthesized from a flow summary.
*/
predicate summaryReadStep(Node pred, ContentSet c, Node succ) {
predicate summaryReadStep(SummaryNode pred, ContentSet c, SummaryNode succ) {
exists(SummarizedCallable sc, SummaryComponentStack s |
pred = summaryNodeInputState(sc, s.tail()) and
succ = summaryNodeInputState(sc, s) and
@@ -801,7 +859,7 @@ module Private {
* Holds if there is a store step of content `c` from `pred` to `succ`, which
* is synthesized from a flow summary.
*/
predicate summaryStoreStep(Node pred, ContentSet c, Node succ) {
predicate summaryStoreStep(SummaryNode pred, ContentSet c, SummaryNode succ) {
exists(SummarizedCallable sc, SummaryComponentStack s |
pred = summaryNodeOutputState(sc, s) and
succ = summaryNodeOutputState(sc, s.tail()) and
@@ -813,7 +871,7 @@ module Private {
* Holds if there is a jump step from `pred` to `succ`, which is synthesized
* from a flow summary.
*/
predicate summaryJumpStep(Node pred, Node succ) {
predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) {
exists(SummaryComponentStack s |
s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and
pred = summaryNodeOutputState(_, s) and
@@ -840,9 +898,9 @@ module Private {
* `a` on line 2 to the post-update node for `a` on that line (via an intermediate
* node where field `b` is cleared).
*/
predicate summaryClearsContent(Node n, ContentSet c) {
predicate summaryClearsContent(SummaryNode n, ContentSet c) {
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
n = summaryNode(sc, state) and
n = TSummaryInternalNode(sc, state) and
state.isInputState(sc, stack) and
stack.head() = SummaryComponent::withoutContent(c)
)
@@ -852,9 +910,9 @@ module Private {
* Holds if the value that is being tracked is expected to be stored inside
* content `c` at `n`.
*/
predicate summaryExpectsContent(Node n, ContentSet c) {
predicate summaryExpectsContent(SummaryNode n, ContentSet c) {
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
n = summaryNode(sc, state) and
n = TSummaryInternalNode(sc, state) and
state.isInputState(sc, stack) and
stack.head() = SummaryComponent::withContent(c)
)
@@ -862,17 +920,17 @@ module Private {
pragma[noinline]
private predicate viableParam(
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, ParamNode p
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p
) {
exists(DataFlowCallable c |
c = inject(sc) and
p.isParameterOf(c, ppos) and
p = TSummaryParameterNode(sc, ppos) and
c = viableCallable(call)
)
}
pragma[nomagic]
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
private SummaryParamNode summaryArgParam(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
exists(ParameterPosition ppos |
argumentPositionMatch(call, arg, ppos) and
viableParam(call, sc, ppos, result)
@@ -884,12 +942,12 @@ module Private {
* local steps. `clearsOrExpects` records whether any node on the path from `p` to
* `n` either clears or expects contents.
*/
private predicate paramReachesLocal(ParamNode p, Node n, boolean clearsOrExpects) {
private predicate paramReachesLocal(SummaryParamNode p, SummaryNode n, boolean clearsOrExpects) {
viableParam(_, _, _, p) and
n = p and
clearsOrExpects = false
or
exists(Node mid, boolean clearsOrExpectsMid |
exists(SummaryNode mid, boolean clearsOrExpectsMid |
paramReachesLocal(p, mid, clearsOrExpectsMid) and
summaryLocalStep(mid, n, true) and
if
@@ -909,21 +967,33 @@ module Private {
*/
pragma[nomagic]
predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) {
exists(ParamNode p, ParameterPosition ppos, Node ret |
exists(SummaryParamNode p, ParameterPosition ppos, SummaryNode ret |
paramReachesLocal(p, ret, true) and
p = summaryArgParam0(_, arg, sc) and
p.isParameterOf(_, pragma[only_bind_into](ppos)) and
p = summaryArgParam(_, arg, sc) and
p = TSummaryParameterNode(_, pragma[only_bind_into](ppos)) and
isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos))
)
}
pragma[nomagic]
private predicate summaryReturnNodeExt(SummaryNode ret, ReturnKindExt rk) {
summaryReturnNode(ret, rk.(ValueReturnKind).getKind())
or
exists(SummaryParamNode p, SummaryNode pre, ParameterPosition pos |
paramReachesLocal(p, pre, _) and
summaryPostUpdateNode(ret, pre) and
p = TSummaryParameterNode(_, pos) and
rk.(ParamUpdateReturnKind).getPosition() = pos
)
}
bindingset[ret]
private ParamNode summaryArgParam(
ArgNode arg, ReturnNodeExt ret, OutNodeExt out, SummarizedCallable sc
private SummaryParamNode summaryArgParamRetOut(
ArgNode arg, SummaryNode ret, OutNodeExt out, SummarizedCallable sc
) {
exists(DataFlowCall call, ReturnKindExt rk |
result = summaryArgParam0(call, arg, sc) and
ret.getKind() = pragma[only_bind_into](rk) and
result = summaryArgParam(call, arg, sc) and
summaryReturnNodeExt(ret, pragma[only_bind_into](rk)) and
out = pragma[only_bind_into](rk).getAnOutNode(call)
)
}
@@ -936,9 +1006,9 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) {
exists(ReturnKind rk, ReturnNode ret, DataFlowCall call |
summaryLocalStep(summaryArgParam0(call, arg, sc), ret, true) and
ret.getKind() = pragma[only_bind_into](rk) and
exists(ReturnKind rk, SummaryNode ret, DataFlowCall call |
summaryLocalStep(summaryArgParam(call, arg, sc), ret, true) and
summaryReturnNode(ret, pragma[only_bind_into](rk)) and
out = getAnOutNode(call, pragma[only_bind_into](rk))
)
}
@@ -951,7 +1021,9 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) {
exists(ReturnNodeExt ret | summaryLocalStep(summaryArgParam(arg, ret, out, sc), ret, false))
exists(SummaryNode ret |
summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), ret, false)
)
}
/**
@@ -962,8 +1034,8 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
exists(Node mid, ReturnNodeExt ret |
summaryReadStep(summaryArgParam(arg, ret, out, sc), c, mid) and
exists(SummaryNode mid, SummaryNode ret |
summaryReadStep(summaryArgParamRetOut(arg, ret, out, sc), c, mid) and
summaryLocalStep(mid, ret, _)
)
}
@@ -976,8 +1048,8 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
exists(Node mid, ReturnNodeExt ret |
summaryLocalStep(summaryArgParam(arg, ret, out, sc), mid, _) and
exists(SummaryNode mid, SummaryNode ret |
summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), mid, _) and
summaryStoreStep(mid, c, ret)
)
}
@@ -1344,11 +1416,11 @@ module Private {
}
private newtype TNodeOrCall =
MkNode(Node n) {
MkNode(SummaryNode n) {
exists(RelevantSummarizedCallable c |
n = summaryNode(c, _)
n = TSummaryInternalNode(c, _)
or
n.(ParamNode).isParameterOf(inject(c), _)
n = TSummaryParameterNode(c, _)
)
} or
MkCall(DataFlowCall call) {
@@ -1357,7 +1429,7 @@ module Private {
}
private class NodeOrCall extends TNodeOrCall {
Node asNode() { this = MkNode(result) }
SummaryNode asNode() { this = MkNode(result) }
DataFlowCall asCall() { this = MkCall(result) }
@@ -1377,9 +1449,11 @@ module Private {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
this.asCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
}

View File

@@ -18,15 +18,15 @@ DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
/** Gets the parameter position representing a callback itself, if any. */
ArgumentPosition callbackSelfParameterPosition() { none() } // disables implicit summary flow to `self` for callbacks
/** Gets the synthesized summary data-flow node for the given values. */
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) }
/** Gets the synthesized data-flow call for `receiver`. */
SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() }
SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() }
/** Gets the type of content `c`. */
DataFlowType getContentType(ContentSet c) { any() }
/** Gets the type of the parameter at the given position. */
DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { any() }
/** Gets the return type of kind `rk` for callable `c`. */
bindingset[c, rk]
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }

View File

@@ -96,7 +96,8 @@ private module Cached {
)
)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
or
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
or

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

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

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

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

View File

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

View File

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

View File

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

@@ -2067,7 +2067,11 @@ module Enumerable {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
output = "Argument[block].Parameter[0]" and
preservesValue = true
or
input = "Argument[block].ReturnValue" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}

View File

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

View File

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

View File

@@ -75,13 +75,34 @@ module UnsafeDeserialization {
}
/**
* An argument in a call to `YAML.load`, considered a sink
* An argument in a call to `YAML.unsafe_*` and `YAML.load_stream` , considered a sink
* for unsafe deserialization. The `YAML` module is an alias of `Psych` in
* recent versions of Ruby.
*/
class YamlLoadArgument extends Sink {
YamlLoadArgument() {
this = API::getTopLevelMember(["YAML", "Psych"]).getAMethodCall("load").getArgument(0)
// Note: this is safe in psych/yaml >= 4.0.0.
this = yamlNode().getAMethodCall("load").getArgument(0)
or
this =
yamlNode().getAMethodCall(["unsafe_load_file", "unsafe_load", "load_stream"]).getArgument(0)
or
this = yamlNode().getAMethodCall(["unsafe_load", "load_stream"]).getKeywordArgument("yaml")
or
this = yamlNode().getAMethodCall("unsafe_load_file").getKeywordArgument("filename")
}
}
private API::Node yamlNode() { result = API::getTopLevelMember(["YAML", "Psych"]) }
/**
* An argument in a call to `YAML.parse*`, considered a sink for unsafe deserialization
* if there is a call to `to_ruby` on the returned value.
*/
class YamlParseArgument extends Sink {
YamlParseArgument() {
this =
yamlNode().getAMethodCall(["parse", "parse_stream", "parse_file"]).getAMethodCall("to_ruby")
}
}
@@ -208,4 +229,23 @@ module UnsafeDeserialization {
)
}
}
/**
* An argument in a call to `Plist.parse_xml` where `marshal` is `true` (which is
* the default), considered a sink for unsafe deserialization.
*/
class UnsafePlistParsexmlArgument extends Sink {
UnsafePlistParsexmlArgument() {
exists(DataFlow::CallNode plistParseXml |
plistParseXml = API::getTopLevelMember("Plist").getAMethodCall("parse_xml")
|
this = [plistParseXml.getArgument(0), plistParseXml.getKeywordArgument("filename_or_xml")] and
(
plistParseXml.getKeywordArgument("marshal").getConstantValue().isBoolean(true)
or
plistParseXml.getNumberOfArguments() = 1
)
)
}
}
}

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())
|
@@ -221,15 +221,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.
@@ -241,19 +233,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
}
/**
@@ -609,26 +595,15 @@ private DataFlow::Node evaluateSummaryComponentStackLocal(
pragma[only_bind_out](tail)) and
stack = SCS::push(pragma[only_bind_out](head), pragma[only_bind_out](tail))
|
exists(
DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos,
DataFlowPrivate::ParameterNodeImpl p
|
exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos |
head = SummaryComponent::parameter(apos) and
DataFlowDispatch::parameterMatch(ppos, apos) and
p.isSourceParameterOf(prev.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(prev.asExpr().getExpr(), ppos)
)
or
exists(DataFlowPrivate::SynthReturnNode ret |
head = SummaryComponent::return() and
ret.getCfgScope() = prev.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()]
)
head = SummaryComponent::return() and
result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() =
prev.asExpr().getExpr()
or
exists(DataFlow::ContentSet content |
head = SummaryComponent::withoutContent(content) and