mirror of
https://github.com/github/codeql.git
synced 2026-04-25 16:55:19 +02:00
Merge branch 'main' into henrymercer/rc-3.11-mergeback
This commit is contained in:
@@ -11,8 +11,6 @@ private module Input implements InputSig<RubyDataFlow> {
|
||||
predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
|
||||
|
||||
predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
n instanceof BlockArgumentNode
|
||||
or
|
||||
n instanceof FlowSummaryNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
@@ -35,11 +33,21 @@ private module Input implements InputSig<RubyDataFlow> {
|
||||
n.asExpr() = arg
|
||||
)
|
||||
}
|
||||
|
||||
predicate multipleArgumentCallExclude(ArgumentNode arg, DataFlowCall call) {
|
||||
// An argument such as `x` in `if not x then ...` has two successors (and hence
|
||||
// two calls); one for each Boolean outcome of `x`.
|
||||
exists(CfgNodes::ExprCfgNode n |
|
||||
arg.argumentOf(call, _) and
|
||||
n = call.asCall() and
|
||||
arg.asExpr().getASuccessor(any(SuccessorTypes::ConditionalSuccessor c)).getASuccessor*() = n and
|
||||
n.getASplit() instanceof Split::ConditionalCompletionSplit
|
||||
)
|
||||
or
|
||||
// Synthetic block parameter nodes are passed directly as lambda-self reference
|
||||
// arguments to all `yield` calls
|
||||
arg instanceof ArgumentNodes::BlockParameterArgumentNode
|
||||
}
|
||||
}
|
||||
|
||||
import MakeConsistency<RubyDataFlow, RubyTaintTracking, Input>
|
||||
|
||||
query predicate multipleToString(DataFlow::Node n, string s) {
|
||||
s = strictconcat(n.toString(), ",") and
|
||||
strictcount(n.toString()) > 1
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import codeql.ruby.ast.Variable
|
||||
import codeql.ruby.dataflow.internal.DataFlowPrivate::VariableCapture::Flow::ConsistencyChecks
|
||||
|
||||
query predicate ambiguousVariable(VariableAccess access, Variable variable) {
|
||||
access.getVariable() = variable and
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Improved support for flow through captured variables that properly adheres to inter-procedural control flow.
|
||||
@@ -27,8 +27,7 @@ private module Impl = Make<Input>;
|
||||
|
||||
class Container = Impl::Container;
|
||||
|
||||
/** A folder. */
|
||||
class Folder extends Container, Impl::Folder { }
|
||||
class Folder = Impl::Folder;
|
||||
|
||||
/** A file. */
|
||||
class File extends Container, Impl::File {
|
||||
|
||||
@@ -886,8 +886,6 @@ module API {
|
||||
)
|
||||
or
|
||||
implicitCallEdge(pred, succ)
|
||||
or
|
||||
exists(DataFlow::HashLiteralNode splat | hashSplatEdge(splat, pred, succ))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -986,29 +984,6 @@ module API {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node getHashSplatArgument(DataFlow::HashLiteralNode literal) {
|
||||
result = DataFlowPrivate::TSynthHashSplatArgumentNode(literal.asExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the epsilon edge `pred -> succ` should be generated to account for the members of a hash literal.
|
||||
*
|
||||
* This currently exists because hash literals are desugared to `Hash.[]` calls, whose summary relies on `WithContent`.
|
||||
* However, `contentEdge` does not currently generate edges for `WithContent` steps.
|
||||
*/
|
||||
bindingset[literal]
|
||||
pragma[inline_late]
|
||||
private predicate hashSplatEdge(DataFlow::HashLiteralNode literal, ApiNode pred, ApiNode succ) {
|
||||
exists(TypeTracker t |
|
||||
pred = Impl::MkForwardNode(getALocalSourceStrict(getHashSplatArgument(literal)), t) and
|
||||
succ = Impl::MkForwardNode(pragma[only_bind_out](literal), pragma[only_bind_out](t))
|
||||
or
|
||||
succ = Impl::MkBackwardNode(getALocalSourceStrict(getHashSplatArgument(literal)), t) and
|
||||
pred = Impl::MkBackwardNode(pragma[only_bind_out](literal), pragma[only_bind_out](t))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode getAModuleReference(DataFlow::ModuleNode mod) {
|
||||
result = mod.getAnImmediateReference()
|
||||
|
||||
@@ -117,6 +117,17 @@ class MethodCall extends Call instanceof MethodCallImpl {
|
||||
*/
|
||||
final Block getBlock() { result = super.getBlockImpl() }
|
||||
|
||||
/**
|
||||
* Gets the block argument of this method call, if any.
|
||||
* ```rb
|
||||
* foo(&block)
|
||||
* ```
|
||||
*/
|
||||
final BlockArgument getBlockArgument() { result = this.getAnArgument() }
|
||||
|
||||
/** Holds if this method call has a block or block argument. */
|
||||
final predicate hasBlock() { exists(this.getBlock()) or exists(this.getBlockArgument()) }
|
||||
|
||||
/**
|
||||
* Holds if the safe navigation operator (`&.`) is used in this call.
|
||||
* ```rb
|
||||
|
||||
@@ -7,7 +7,6 @@ private import codeql.ruby.ast.internal.Constant
|
||||
private import codeql.ruby.ast.internal.Literal
|
||||
private import ControlFlowGraph
|
||||
private import internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import internal.Splitting
|
||||
|
||||
/** An entry node for a given scope. */
|
||||
class EntryNode extends CfgNode, CfgImpl::EntryNode {
|
||||
@@ -201,8 +200,15 @@ module ExprNodes {
|
||||
|
||||
override LhsExpr getExpr() { result = super.getExpr() }
|
||||
|
||||
/** Gets a variable used in (or introduced by) this LHS. */
|
||||
Variable getAVariable() { result = e.(VariableAccess).getVariable() }
|
||||
/**
|
||||
* DEPRECATED: use `getVariable` instead.
|
||||
*
|
||||
* Gets a variable used in (or introduced by) this LHS.
|
||||
*/
|
||||
deprecated Variable getAVariable() { result = e.(VariableAccess).getVariable() }
|
||||
|
||||
/** Gets the variable used in (or introduced by) this LHS. */
|
||||
Variable getVariable() { result = e.(VariableAccess).getVariable() }
|
||||
}
|
||||
|
||||
private class AssignExprChildMapping extends ExprChildMapping, AssignExpr {
|
||||
|
||||
@@ -4,7 +4,7 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.BasicBlocks
|
||||
private import SuccessorTypes
|
||||
private import internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import internal.Splitting
|
||||
private import internal.Splitting as Splitting
|
||||
private import internal.Completion
|
||||
|
||||
/**
|
||||
@@ -293,3 +293,10 @@ module SuccessorTypes {
|
||||
final override string toString() { result = "exit" }
|
||||
}
|
||||
}
|
||||
|
||||
class Split = Splitting::Split;
|
||||
|
||||
/** Provides different kinds of control flow graph splittings. */
|
||||
module Split {
|
||||
class ConditionalCompletionSplit = Splitting::ConditionalCompletionSplit;
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ private module ConditionalCompletionSplitting {
|
||||
}
|
||||
}
|
||||
|
||||
class ConditionalCompletionSplit = ConditionalCompletionSplitting::ConditionalCompletionSplit;
|
||||
|
||||
module EnsureSplitting {
|
||||
/**
|
||||
* The type of a split `ensure` node.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** Provides classes and predicates for defining flow summaries. */
|
||||
|
||||
import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
import codeql.ruby.DataFlow
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowDispatch
|
||||
@@ -158,3 +160,57 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
|
||||
}
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
|
||||
/**
|
||||
* Provides a set of special flow summaries to ensure that callbacks passed into
|
||||
* library methods will be passed as `lambda-self` arguments into themselves. That is,
|
||||
* we are assuming that callbacks passed into library methods will be called, which is
|
||||
* needed for flow through captured variables.
|
||||
*/
|
||||
private module LibraryCallbackSummaries {
|
||||
private predicate libraryCall(CfgNodes::ExprNodes::CallCfgNode call) {
|
||||
not exists(getTarget(call))
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackLambdaCreation(TypeTracker t) {
|
||||
t.start() and
|
||||
lambdaCreation(result, TLambdaCallKind(), _)
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackLambdaCreation(t2).track(t2, t)) and
|
||||
not result instanceof DataFlow::SelfParameterNode
|
||||
}
|
||||
|
||||
private predicate libraryCallHasLambdaArg(CfgNodes::ExprNodes::CallCfgNode call, int i) {
|
||||
exists(CfgNodes::ExprCfgNode arg |
|
||||
arg = call.getArgument(i) and
|
||||
arg = trackLambdaCreation(TypeTracker::end()).getALocalUse().asExpr() and
|
||||
libraryCall(call) and
|
||||
not arg instanceof CfgNodes::ExprNodes::BlockArgumentCfgNode
|
||||
)
|
||||
}
|
||||
|
||||
private class LibraryLambdaMethod extends SummarizedCallable {
|
||||
LibraryLambdaMethod() { this = "<library method accepting a callback>" }
|
||||
|
||||
final override MethodCall getACall() {
|
||||
libraryCall(result.getAControlFlowNode()) and
|
||||
result.hasBlock()
|
||||
or
|
||||
libraryCallHasLambdaArg(result.getAControlFlowNode(), _)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[block]" and
|
||||
output = "Argument[block].Parameter[lambda-self]"
|
||||
or
|
||||
exists(int i |
|
||||
i in [0 .. 10] and
|
||||
input = "Argument[" + i + "]" and
|
||||
output = "Argument[" + i + "].Parameter[lambda-self]"
|
||||
)
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
|
||||
SelfLocalSourceNode() {
|
||||
self = this.(SelfParameterNodeImpl).getSelfVariable()
|
||||
or
|
||||
self = this.(SsaSelfDefinitionNode).getVariable() and
|
||||
not LocalFlow::localFlowSsaParamInput(_, this)
|
||||
self = this.(SsaSelfDefinitionNode).getVariable()
|
||||
}
|
||||
|
||||
/** Gets the `self` variable. */
|
||||
@@ -310,8 +309,14 @@ predicate isUserDefinedNew(SingletonMethod new) {
|
||||
}
|
||||
|
||||
private Callable viableSourceCallableNonInit(RelevantCall call) {
|
||||
result = getTarget(call) and
|
||||
not result = blockCall(call) // handled by `lambdaCreation`/`lambdaCall`
|
||||
result = getTargetInstance(call, _)
|
||||
or
|
||||
result = getTargetSingleton(call, _)
|
||||
or
|
||||
exists(Module cls, string method |
|
||||
superCall(call, cls, method) and
|
||||
result = lookupMethod(cls.getAnImmediateAncestor(), method)
|
||||
)
|
||||
}
|
||||
|
||||
private Callable viableSourceCallableInit(RelevantCall call) { result = getInitializeTarget(call) }
|
||||
@@ -401,14 +406,7 @@ private module Cached {
|
||||
|
||||
cached
|
||||
CfgScope getTarget(RelevantCall call) {
|
||||
result = getTargetInstance(call, _)
|
||||
or
|
||||
result = getTargetSingleton(call, _)
|
||||
or
|
||||
exists(Module cls, string method |
|
||||
superCall(call, cls, method) and
|
||||
result = lookupMethod(cls.getAnImmediateAncestor(), method)
|
||||
)
|
||||
result = viableSourceCallableNonInit(call)
|
||||
or
|
||||
result = blockCall(call)
|
||||
}
|
||||
@@ -424,6 +422,7 @@ private module Cached {
|
||||
cached
|
||||
newtype TArgumentPosition =
|
||||
TSelfArgumentPosition() or
|
||||
TLambdaSelfArgumentPosition() or
|
||||
TBlockArgumentPosition() or
|
||||
TPositionalArgumentPosition(int pos) {
|
||||
exists(Call c | exists(c.getArgument(pos)))
|
||||
@@ -438,6 +437,7 @@ private module Cached {
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
|
||||
} or
|
||||
THashSplatArgumentPosition() or
|
||||
TSynthHashSplatArgumentPosition() or
|
||||
TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or
|
||||
TSynthSplatArgumentPosition() or
|
||||
TAnyArgumentPosition() or
|
||||
@@ -446,6 +446,7 @@ private module Cached {
|
||||
cached
|
||||
newtype TParameterPosition =
|
||||
TSelfParameterPosition() or
|
||||
TLambdaSelfParameterPosition() or
|
||||
TBlockParameterPosition() or
|
||||
TPositionalParameterPosition(int pos) {
|
||||
pos = any(Parameter p).getPosition()
|
||||
@@ -461,18 +462,13 @@ private module Cached {
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
|
||||
} or
|
||||
THashSplatParameterPosition() or
|
||||
// To get flow from a hash-splat argument to a keyword parameter, we add a read-step
|
||||
// from a synthetic hash-splat parameter. We need this separate synthetic ParameterNode,
|
||||
// since we clear content of the normal hash-splat parameter for the names that
|
||||
// correspond to normal keyword parameters. Since we cannot re-use the same parameter
|
||||
// position for multiple parameter nodes in the same callable, we introduce this
|
||||
// synthetic parameter position.
|
||||
TSynthHashSplatParameterPosition() or
|
||||
TSplatParameterPosition(int pos) {
|
||||
pos = 0
|
||||
or
|
||||
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
|
||||
} or
|
||||
TSynthSplatParameterPosition() or
|
||||
TSynthArgSplatParameterPosition() or
|
||||
TAnyParameterPosition() or
|
||||
TAnyKeywordParameterPosition()
|
||||
}
|
||||
@@ -941,20 +937,24 @@ private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruct
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
|
||||
exists(
|
||||
RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p,
|
||||
CfgNodes::ExprCfgNode nodeFromPreExpr
|
||||
|
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
nodeFromPreExpr = nodeFrom.asExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr.getExpr())
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
nodeFromPreExpr =
|
||||
LocalFlow::getParameterDefNode(p.getParameter()).getDefinitionExt().getARead()
|
||||
or
|
||||
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
|
||||
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfDefinition().getARead()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1276,6 +1276,9 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a `self` parameter. */
|
||||
predicate isSelf() { this = TSelfParameterPosition() }
|
||||
|
||||
/** Holds if this position represents a reference to a lambda itself. Only used for tracking flow through captured variables. */
|
||||
predicate isLambdaSelf() { this = TLambdaSelfParameterPosition() }
|
||||
|
||||
/** Holds if this position represents a block parameter. */
|
||||
predicate isBlock() { this = TBlockParameterPosition() }
|
||||
|
||||
@@ -1291,15 +1294,15 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a hash-splat parameter. */
|
||||
predicate isHashSplat() { this = THashSplatParameterPosition() }
|
||||
|
||||
/** Holds if this position represents a synthetic hash-splat parameter. */
|
||||
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
|
||||
|
||||
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
|
||||
|
||||
// A fake position to indicate that this parameter node holds content from a synth arg splat node
|
||||
predicate isSynthArgSplat() { this = TSynthArgSplatParameterPosition() }
|
||||
|
||||
/** Holds if this position represents a splat parameter at position `n`. */
|
||||
predicate isSplat(int n) { this = TSplatParameterPosition(n) }
|
||||
|
||||
/** Holds if this position represents a synthetic splat parameter. */
|
||||
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
|
||||
|
||||
/**
|
||||
* Holds if this position represents any parameter, except `self` parameters. This
|
||||
* includes both positional, named, and block parameters.
|
||||
@@ -1313,6 +1316,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
or
|
||||
this.isLambdaSelf() and result = "lambda self"
|
||||
or
|
||||
this.isBlock() and result = "block"
|
||||
or
|
||||
exists(int pos | this.isPositional(pos) and result = "position " + pos)
|
||||
@@ -1329,11 +1334,9 @@ class ParameterPosition extends TParameterPosition {
|
||||
or
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
or
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
or
|
||||
this.isSynthArgSplat() and result = "synthetic * (from *args)"
|
||||
or
|
||||
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
|
||||
or
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,6 +1345,9 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
/** Holds if this position represents a `self` argument. */
|
||||
predicate isSelf() { this = TSelfArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents a lambda `self` argument. Only used for tracking flow through captured variables. */
|
||||
predicate isLambdaSelf() { this = TLambdaSelfArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents a block argument. */
|
||||
predicate isBlock() { this = TBlockArgumentPosition() }
|
||||
|
||||
@@ -1360,20 +1366,24 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
/** Holds if this position represents any positional parameter. */
|
||||
predicate isAnyNamed() { this = TAnyKeywordArgumentPosition() }
|
||||
|
||||
/**
|
||||
* Holds if this position represents a synthesized argument containing all keyword
|
||||
* arguments wrapped in a hash.
|
||||
*/
|
||||
/** Holds if this position represents a hash-splat argument. */
|
||||
predicate isHashSplat() { this = THashSplatArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents a synthetic hash-splat argument. */
|
||||
predicate isSynthHashSplat() { this = TSynthHashSplatArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents a splat argument at position `n`. */
|
||||
predicate isSplat(int n) { this = TSplatArgumentPosition(n) }
|
||||
|
||||
/** Holds if this position represents a synthetic splat argument. */
|
||||
predicate isSynthSplat() { this = TSynthSplatArgumentPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
or
|
||||
this.isLambdaSelf() and result = "lambda self"
|
||||
or
|
||||
this.isBlock() and result = "block"
|
||||
or
|
||||
exists(int pos | this.isPositional(pos) and result = "position " + pos)
|
||||
@@ -1386,23 +1396,33 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
or
|
||||
this.isHashSplat() and result = "**"
|
||||
or
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
this.isSynthHashSplat() and result = "synthetic **"
|
||||
or
|
||||
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
|
||||
or
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) { not ppos.isSelf() }
|
||||
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) {
|
||||
not ppos.isSelf() and
|
||||
not ppos.isLambdaSelf()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) { not apos.isSelf() }
|
||||
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) {
|
||||
not apos.isSelf() and
|
||||
not apos.isLambdaSelf()
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[nomagic]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
ppos.isSelf() and apos.isSelf()
|
||||
or
|
||||
ppos.isLambdaSelf() and apos.isLambdaSelf()
|
||||
or
|
||||
ppos.isBlock() and apos.isBlock()
|
||||
or
|
||||
exists(int pos | ppos.isPositional(pos) and apos.isPositional(pos))
|
||||
@@ -1413,18 +1433,21 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
|
||||
or
|
||||
ppos.isHashSplat() and apos.isHashSplat()
|
||||
(ppos.isHashSplat() or ppos.isSynthHashSplat()) and
|
||||
(apos.isHashSplat() or apos.isSynthHashSplat())
|
||||
or
|
||||
ppos.isSynthHashSplat() and apos.isHashSplat()
|
||||
or
|
||||
ppos.isSplat(0) and apos.isSynthSplat()
|
||||
or
|
||||
ppos.isSynthSplat() and apos.isSplat(0)
|
||||
or
|
||||
apos.isSynthSplat() and ppos.isSynthArgSplat()
|
||||
or
|
||||
// Exact splat match
|
||||
exists(int n | apos.isSplat(n) and ppos.isSplat(n))
|
||||
exists(int pos |
|
||||
(
|
||||
ppos.isSplat(pos)
|
||||
or
|
||||
ppos.isSynthSplat() and pos = 0
|
||||
) and
|
||||
(
|
||||
apos.isSplat(pos)
|
||||
or
|
||||
apos.isSynthSplat() and pos = 0
|
||||
)
|
||||
)
|
||||
or
|
||||
ppos.isAny() and argumentPositionIsNotSelf(apos)
|
||||
or
|
||||
@@ -1434,15 +1457,3 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
apos.isAnyNamed() and ppos.isKeyword(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow from `call`'s argument `arg` to parameter `p` is permissible.
|
||||
*
|
||||
* This is a temporary hook to support technical debt in the Go language; do not use.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate golangSpecificParamArgFilter(
|
||||
DataFlowCall call, DataFlow::ParameterNode p, ArgumentNode arg
|
||||
) {
|
||||
any()
|
||||
}
|
||||
|
||||
@@ -297,6 +297,10 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,6 +297,10 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED: Use `Global` and `GlobalWithState` instead.
|
||||
*
|
||||
* Provides a `Configuration` class backwards-compatible interface to the data
|
||||
* flow library.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
private import DataFlowImpl
|
||||
import DataFlowImplCommonPublic
|
||||
import FlowStateString
|
||||
private import codeql.util.Unit
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the global data flow library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with
|
||||
* a subclass whose characteristic predicate is a unique singleton string.
|
||||
* For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isBarrier`.
|
||||
* // Optionally override `isAdditionalFlowStep`.
|
||||
* }
|
||||
* ```
|
||||
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
||||
* the edges are those data-flow steps that preserve the value of the node
|
||||
* along with any additional edges defined by `isAdditionalFlowStep`.
|
||||
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
||||
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
||||
* and/or out-going edges from those nodes, respectively.
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other. One of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
||||
*/
|
||||
abstract class Configuration extends string {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to prevent mutual recursion between the user-overridden
|
||||
* member predicates of `Configuration` and the rest of the data-flow library.
|
||||
* Good performance cannot be guaranteed in the presence of such recursion, so
|
||||
* it should be replaced by using more than one copy of the data flow library.
|
||||
*/
|
||||
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
||||
bindingset[this]
|
||||
ConfigurationRecursionPrevention() { any() }
|
||||
|
||||
override predicate hasFlow(Node source, Node sink) {
|
||||
strictcount(Node n | this.isSource(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSource(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
||||
or
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private FlowState relevantState(Configuration config) {
|
||||
config.isSource(_, result) or
|
||||
config.isSink(_, result) or
|
||||
config.isBarrier(_, result) or
|
||||
config.isAdditionalFlowStep(_, result, _, _) or
|
||||
config.isAdditionalFlowStep(_, _, _, result)
|
||||
}
|
||||
|
||||
private newtype TConfigState =
|
||||
TMkConfigState(Configuration config, FlowState state) {
|
||||
state = relevantState(config) or state instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
|
||||
|
||||
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
|
||||
|
||||
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
|
||||
|
||||
private module Config implements FullStateConfigSig {
|
||||
class FlowState = TConfigState;
|
||||
|
||||
predicate isSource(Node source, FlowState state) {
|
||||
getConfig(state).isSource(source, getState(state))
|
||||
or
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
predicate isBarrier(Node node, FlowState state) {
|
||||
getConfig(state).isBarrier(node, getState(state)) or
|
||||
getConfig(state).isBarrier(node) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
|
||||
}
|
||||
|
||||
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
|
||||
getConfig(state2) = getConfig(state1)
|
||||
or
|
||||
not singleConfiguration() and
|
||||
getConfig(state1).isAdditionalFlowStep(node1, node2) and
|
||||
state2 = state1
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(Node node, ContentSet c) {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
}
|
||||
|
||||
private import Impl<Config> as I
|
||||
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode instanceof I::PathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of this element, including a textual
|
||||
* representation of the call context.
|
||||
*/
|
||||
final string toStringWithContext() { result = super.toStringWithContext() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
final predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
final Node getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the `FlowState` of this node. */
|
||||
final FlowState getState() { result = getState(super.getState()) }
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
final Configuration getConfiguration() { result = getConfig(super.getState()) }
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
final predicate isSource() { super.isSource() }
|
||||
|
||||
/** Holds if this node is a grouping of source nodes. */
|
||||
final predicate isSourceGroup(string group) { super.isSourceGroup(group) }
|
||||
|
||||
/** Holds if this node is a grouping of sink nodes. */
|
||||
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
|
||||
}
|
||||
|
||||
module PathGraph = I::PathGraph;
|
||||
|
||||
private predicate hasFlow(Node source, Node sink, Configuration config) {
|
||||
exists(PathNode source0, PathNode sink0 |
|
||||
hasFlowPath(source0, sink0, config) and
|
||||
source0.getNode() = source and
|
||||
sink0.getNode() = sink
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
|
||||
I::flowPath(source, sink) and source.getConfiguration() = config
|
||||
}
|
||||
|
||||
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
|
||||
|
||||
predicate flowsTo = hasFlow/3;
|
||||
@@ -1,402 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED: Use `Global` and `GlobalWithState` instead.
|
||||
*
|
||||
* Provides a `Configuration` class backwards-compatible interface to the data
|
||||
* flow library.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
private import DataFlowImpl
|
||||
import DataFlowImplCommonPublic
|
||||
import FlowStateString
|
||||
private import codeql.util.Unit
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the global data flow library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with
|
||||
* a subclass whose characteristic predicate is a unique singleton string.
|
||||
* For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isBarrier`.
|
||||
* // Optionally override `isAdditionalFlowStep`.
|
||||
* }
|
||||
* ```
|
||||
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
||||
* the edges are those data-flow steps that preserve the value of the node
|
||||
* along with any additional edges defined by `isAdditionalFlowStep`.
|
||||
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
||||
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
||||
* and/or out-going edges from those nodes, respectively.
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other. One of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
||||
*/
|
||||
abstract class Configuration extends string {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to prevent mutual recursion between the user-overridden
|
||||
* member predicates of `Configuration` and the rest of the data-flow library.
|
||||
* Good performance cannot be guaranteed in the presence of such recursion, so
|
||||
* it should be replaced by using more than one copy of the data flow library.
|
||||
*/
|
||||
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
||||
bindingset[this]
|
||||
ConfigurationRecursionPrevention() { any() }
|
||||
|
||||
override predicate hasFlow(Node source, Node sink) {
|
||||
strictcount(Node n | this.isSource(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSource(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
||||
or
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private FlowState relevantState(Configuration config) {
|
||||
config.isSource(_, result) or
|
||||
config.isSink(_, result) or
|
||||
config.isBarrier(_, result) or
|
||||
config.isAdditionalFlowStep(_, result, _, _) or
|
||||
config.isAdditionalFlowStep(_, _, _, result)
|
||||
}
|
||||
|
||||
private newtype TConfigState =
|
||||
TMkConfigState(Configuration config, FlowState state) {
|
||||
state = relevantState(config) or state instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
|
||||
|
||||
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
|
||||
|
||||
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
|
||||
|
||||
private module Config implements FullStateConfigSig {
|
||||
class FlowState = TConfigState;
|
||||
|
||||
predicate isSource(Node source, FlowState state) {
|
||||
getConfig(state).isSource(source, getState(state))
|
||||
or
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
predicate isBarrier(Node node, FlowState state) {
|
||||
getConfig(state).isBarrier(node, getState(state)) or
|
||||
getConfig(state).isBarrier(node) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
|
||||
}
|
||||
|
||||
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
|
||||
getConfig(state2) = getConfig(state1)
|
||||
or
|
||||
not singleConfiguration() and
|
||||
getConfig(state1).isAdditionalFlowStep(node1, node2) and
|
||||
state2 = state1
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(Node node, ContentSet c) {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
}
|
||||
|
||||
private import Impl<Config> as I
|
||||
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode instanceof I::PathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of this element, including a textual
|
||||
* representation of the call context.
|
||||
*/
|
||||
final string toStringWithContext() { result = super.toStringWithContext() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
final predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
final Node getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the `FlowState` of this node. */
|
||||
final FlowState getState() { result = getState(super.getState()) }
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
final Configuration getConfiguration() { result = getConfig(super.getState()) }
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
final predicate isSource() { super.isSource() }
|
||||
|
||||
/** Holds if this node is a grouping of source nodes. */
|
||||
final predicate isSourceGroup(string group) { super.isSourceGroup(group) }
|
||||
|
||||
/** Holds if this node is a grouping of sink nodes. */
|
||||
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
|
||||
}
|
||||
|
||||
module PathGraph = I::PathGraph;
|
||||
|
||||
private predicate hasFlow(Node source, Node sink, Configuration config) {
|
||||
exists(PathNode source0, PathNode sink0 |
|
||||
hasFlowPath(source0, sink0, config) and
|
||||
source0.getNode() = source and
|
||||
sink0.getNode() = sink
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
|
||||
I::flowPath(source, sink) and source.getConfiguration() = config
|
||||
}
|
||||
|
||||
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
|
||||
|
||||
predicate flowsTo = hasFlow/3;
|
||||
@@ -17,6 +17,9 @@ module RubyDataFlow implements InputSig {
|
||||
import Private
|
||||
import Public
|
||||
|
||||
// includes `LambdaSelfReferenceNode`, which is not part of the public API
|
||||
class ParameterNode = Private::ParameterNodeImpl;
|
||||
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
Private::isParameterNode(p, c, pos)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -549,6 +549,73 @@ module Content {
|
||||
result = getElementContent(e.getConstantValue()).(KnownElementContent).getIndex()
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* An element inside a synthetic splat argument. All positional arguments
|
||||
* (including splat arguments) are implicitly stored inside a synthetic
|
||||
* splat argument. For example, in
|
||||
*
|
||||
* ```rb
|
||||
* foo(1, 2, 3)
|
||||
* ```
|
||||
*
|
||||
* we have an implicit splat argument containing `[1, 2, 3]`.
|
||||
*/
|
||||
class SplatContent extends ElementContent, TSplatContent {
|
||||
private int i;
|
||||
private boolean shifted;
|
||||
|
||||
SplatContent() { this = TSplatContent(i, shifted) }
|
||||
|
||||
/** Gets the position of this splat element. */
|
||||
int getPosition() { result = i }
|
||||
|
||||
/**
|
||||
* Holds if this element represents a value from an actual splat argument
|
||||
* that had its index shifted. For example, in
|
||||
*
|
||||
* ```rb
|
||||
* foo(x, *args)
|
||||
* ```
|
||||
*
|
||||
* the elements of `args` will have their index shifted by 1 before being
|
||||
* put into the synthetic splat argument.
|
||||
*/
|
||||
predicate isShifted() { shifted = true }
|
||||
|
||||
override string toString() {
|
||||
exists(string s |
|
||||
(if this.isShifted() then s = " (shifted)" else s = "") and
|
||||
result = "splat position " + i + s
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* An element inside a synthetic hash-splat argument. All keyword arguments
|
||||
* are implicitly stored inside a synthetic hash-splat argument. For example,
|
||||
* in
|
||||
*
|
||||
* ```rb
|
||||
* foo(a: 1, b: 2, c: 3)
|
||||
* ```
|
||||
*
|
||||
* we have an implicit hash-splat argument containing `{:a => 1, :b => 2, :c => 3}`.
|
||||
*/
|
||||
class HashSplatContent extends ElementContent, THashSplatContent {
|
||||
private ConstantValue cv;
|
||||
|
||||
HashSplatContent() { this = THashSplatContent(cv) }
|
||||
|
||||
/** Gets the hash key. */
|
||||
ConstantValue getKey() { result = cv }
|
||||
|
||||
override string toString() { result = "hash-splat position " + cv }
|
||||
}
|
||||
|
||||
/**
|
||||
* A value stored behind a getter/setter pair.
|
||||
*
|
||||
@@ -568,6 +635,18 @@ module Content {
|
||||
|
||||
/** Gets `AttributeNameContent` of the given name. */
|
||||
AttributeNameContent getAttributeName(string name) { result.getName() = name }
|
||||
|
||||
/** A captured variable. */
|
||||
class CapturedVariableContent extends Content, TCapturedVariableContent {
|
||||
private LocalVariable v;
|
||||
|
||||
CapturedVariableContent() { this = TCapturedVariableContent(v) }
|
||||
|
||||
/** Gets the captured variable. */
|
||||
LocalVariable getVariable() { result = v }
|
||||
|
||||
override string toString() { result = "captured " + v }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,6 +772,8 @@ class ContentSet extends TContentSet {
|
||||
or
|
||||
exists(Content::KnownElementContent c | this.isKnownOrUnknownElement(c) |
|
||||
result = c or
|
||||
result = TSplatContent(c.getIndex().getInt(), _) or
|
||||
result = THashSplatContent(c.getIndex()) or
|
||||
result = TUnknownElementContent()
|
||||
)
|
||||
or
|
||||
@@ -700,7 +781,9 @@ class ContentSet extends TContentSet {
|
||||
this = TElementLowerBoundContent(lower, includeUnknown)
|
||||
|
|
||||
exists(int i |
|
||||
result.(Content::KnownElementContent).getIndex().isInt(i) and
|
||||
result.(Content::KnownElementContent).getIndex().isInt(i) or
|
||||
result = TSplatContent(i, _)
|
||||
|
|
||||
i >= lower
|
||||
)
|
||||
or
|
||||
@@ -713,6 +796,11 @@ class ContentSet extends TContentSet {
|
||||
|
|
||||
type = result.(Content::KnownElementContent).getIndex().getValueType()
|
||||
or
|
||||
type = "int" and
|
||||
result instanceof Content::SplatContent
|
||||
or
|
||||
type = result.(Content::HashSplatContent).getKey().getValueType()
|
||||
or
|
||||
includeUnknown = true and
|
||||
result = TUnknownElementContent()
|
||||
)
|
||||
@@ -728,6 +816,12 @@ class ContentSet extends TContentSet {
|
||||
*/
|
||||
signature predicate guardChecksSig(CfgNodes::AstCfgNode g, CfgNode e, boolean branch);
|
||||
|
||||
bindingset[def1, def2]
|
||||
pragma[inline_late]
|
||||
private predicate sameSourceVariable(Ssa::Definition def1, Ssa::Definition def2) {
|
||||
def1.getSourceVariable() = def2.getSourceVariable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates an expression.
|
||||
*
|
||||
@@ -765,16 +859,16 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
* This is restricted to calls where the variable is captured inside a
|
||||
* block.
|
||||
*/
|
||||
private Ssa::Definition getAMaybeGuardedCapturedDef() {
|
||||
private Ssa::CapturedEntryDefinition getAMaybeGuardedCapturedDef() {
|
||||
exists(
|
||||
CfgNodes::ExprCfgNode g, boolean branch, CfgNodes::ExprCfgNode testedNode,
|
||||
Ssa::Definition def, CfgNodes::ExprNodes::CallCfgNode call
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
guardChecks(g, testedNode, branch) and
|
||||
SsaImpl::captureFlowIn(call, def, result) and
|
||||
guardControlsBlock(g, call.getBasicBlock(), branch) and
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock() and
|
||||
sameSourceVariable(def, result)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -830,16 +924,16 @@ abstract deprecated class BarrierGuard extends CfgNodes::ExprCfgNode {
|
||||
* This is restricted to calls where the variable is captured inside a
|
||||
* block.
|
||||
*/
|
||||
private Ssa::Definition getAMaybeGuardedCapturedDef() {
|
||||
private Ssa::CapturedEntryDefinition getAMaybeGuardedCapturedDef() {
|
||||
exists(
|
||||
boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def,
|
||||
CfgNodes::ExprNodes::CallCfgNode call
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
this.checks(testedNode, branch) and
|
||||
SsaImpl::captureFlowIn(call, def, result) and
|
||||
this.controlsBlock(call.getBasicBlock(), branch) and
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock() and
|
||||
sameSourceVariable(def, result)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1207,8 +1301,15 @@ class LhsExprNode extends ExprNode {
|
||||
/** Gets the underlying AST node as a `LhsExpr`. */
|
||||
LhsExpr asLhsExprAstNode() { result = lhsExprCfgNode.getExpr() }
|
||||
|
||||
/** Gets a variable used in (or introduced by) this LHS. */
|
||||
Variable getAVariable() { result = lhsExprCfgNode.getAVariable() }
|
||||
/**
|
||||
* DEPRECATED: use `getVariable` instead.
|
||||
*
|
||||
* Gets a variable used in (or introduced by) this LHS.
|
||||
*/
|
||||
deprecated Variable getAVariable() { result = lhsExprCfgNode.getAVariable() }
|
||||
|
||||
/** Gets the variable used in (or introduced by) this LHS. */
|
||||
Variable getVariable() { result = lhsExprCfgNode.getVariable() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ class NeutralCallableBase = string;
|
||||
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
|
||||
ArgumentPosition callbackSelfParameterPosition() { result.isLambdaSelf() }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() }
|
||||
@@ -128,6 +128,9 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
or
|
||||
arg = "hash-splat" and
|
||||
ppos.isHashSplat()
|
||||
or
|
||||
arg = "splat" and
|
||||
ppos.isSplat(0)
|
||||
)
|
||||
or
|
||||
result = interpretElementArg(c.getAnArgument("Element"))
|
||||
@@ -215,6 +218,9 @@ string getParameterPosition(ParameterPosition pos) {
|
||||
pos.isSelf() and
|
||||
result = "self"
|
||||
or
|
||||
pos.isLambdaSelf() and
|
||||
result = "lambda-self"
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = "block"
|
||||
or
|
||||
@@ -226,12 +232,17 @@ string getParameterPosition(ParameterPosition pos) {
|
||||
or
|
||||
pos.isHashSplat() and
|
||||
result = "hash-splat"
|
||||
or
|
||||
pos.isSplat(0) and
|
||||
result = "splat"
|
||||
}
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
string getArgumentPosition(ArgumentPosition pos) {
|
||||
pos.isSelf() and result = "self"
|
||||
or
|
||||
pos.isLambdaSelf() and result = "lambda-self"
|
||||
or
|
||||
pos.isBlock() and result = "block"
|
||||
or
|
||||
exists(int i |
|
||||
@@ -372,6 +383,9 @@ ArgumentPosition parseParamBody(string s) {
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
or
|
||||
s = "lambda-self" and
|
||||
result.isLambdaSelf()
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
or
|
||||
@@ -402,6 +416,9 @@ ParameterPosition parseArgBody(string s) {
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
or
|
||||
s = "lambda-self" and
|
||||
result.isLambdaSelf()
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
or
|
||||
|
||||
@@ -363,97 +363,8 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate defReachesCallReadInOuterScope(
|
||||
Definition def, CallCfgNode call, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedCallRead(call, bb, i, v) and
|
||||
scope.getOuterCfgScope() = bb.getScope()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, Cfg::CfgScope scope) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
capturedEntryWrite(bb, i, v) and
|
||||
entry.definesAt(v, bb, i) and
|
||||
bb.getScope().getOuterCfgScope*() = scope
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is flow for a captured variable from the enclosing scope into a block.
|
||||
* ```rb
|
||||
* foo = 0
|
||||
* bar {
|
||||
* puts foo
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
predicate captureFlowIn(CallCfgNode call, Definition def, Definition entry) {
|
||||
exists(LocalVariable v, Cfg::CfgScope scope |
|
||||
defReachesCallReadInOuterScope(def, call, v, scope) and
|
||||
hasCapturedEntryWrite(entry, v, scope)
|
||||
|
|
||||
// If the read happens inside a block, we restrict to the call that
|
||||
// contains the block
|
||||
not scope instanceof Block
|
||||
or
|
||||
scope = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
}
|
||||
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
pragma[noinline]
|
||||
private predicate defReachesExitReadInInnerScope(
|
||||
Definition def, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedExitRead(bb, i, v) and
|
||||
scope = bb.getScope().getOuterCfgScope*()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedExitRead(
|
||||
Definition exit, CallCfgNode call, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
capturedCallWrite(call, bb, i, v) and
|
||||
exit.definesAt(v, bb, i) and
|
||||
bb.getScope() = scope.getOuterCfgScope()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is outgoing flow for a captured variable that is updated in a block.
|
||||
* ```rb
|
||||
* foo = 0
|
||||
* bar {
|
||||
* foo += 10
|
||||
* }
|
||||
* puts foo
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
predicate captureFlowOut(CallCfgNode call, Definition def, Definition exit) {
|
||||
exists(LocalVariable v, Cfg::CfgScope scope |
|
||||
defReachesExitReadInInnerScope(def, v, scope) and
|
||||
hasCapturedExitRead(exit, call, v, _)
|
||||
|
|
||||
// If the read happens inside a block, we restrict to the call that
|
||||
// contains the block
|
||||
not scope instanceof Block
|
||||
or
|
||||
scope = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
Definition phiHasInputFromBlock(PhiNode phi, Cfg::BasicBlock bb) {
|
||||
Impl::phiHasInputFromBlock(phi, result, bb)
|
||||
@@ -570,6 +481,8 @@ import Cached
|
||||
* Only intended for internal use.
|
||||
*/
|
||||
class DefinitionExt extends Impl::DefinitionExt {
|
||||
VariableReadAccessCfgNode getARead() { result = getARead(this) }
|
||||
|
||||
override string toString() { result = this.(Ssa::Definition).toString() }
|
||||
|
||||
/** Gets the location of this definition. */
|
||||
|
||||
@@ -17,7 +17,10 @@ predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||
* of `c` at sinks and inputs to additional taint steps.
|
||||
*/
|
||||
bindingset[node]
|
||||
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) { none() }
|
||||
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
exists(node) and
|
||||
c.isElementOfTypeOrUnknown("int")
|
||||
}
|
||||
|
||||
private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
|
||||
CfgNodes::ExprNodes::CasePatternCfgNode p
|
||||
@@ -64,9 +67,10 @@ private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
|
||||
cached
|
||||
private module Cached {
|
||||
private import codeql.ruby.dataflow.FlowSteps as FlowSteps
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
|
||||
|
||||
cached
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
predicate forceCachingInSameStage() { DataFlowImplCommon::forceCachingInSameStage() }
|
||||
|
||||
/**
|
||||
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
|
||||
|
||||
@@ -9,32 +9,80 @@ private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.ApiGraphs
|
||||
import UnicodeBypassValidationCustomizations::UnicodeBypassValidation
|
||||
|
||||
/** A state signifying that a logical validation has not been performed. */
|
||||
class PreValidation extends DataFlow::FlowState {
|
||||
/**
|
||||
* A state signifying that a logical validation has not been performed.
|
||||
* DEPRECATED: Use `PreValidationState()`
|
||||
*/
|
||||
deprecated class PreValidation extends DataFlow::FlowState {
|
||||
PreValidation() { this = "PreValidation" }
|
||||
}
|
||||
|
||||
/** A state signifying that a logical validation has been performed. */
|
||||
class PostValidation extends DataFlow::FlowState {
|
||||
/**
|
||||
* A state signifying that a logical validation has been performed.
|
||||
* DEPRECATED: Use `PostValidationState()`
|
||||
*/
|
||||
deprecated class PostValidation extends DataFlow::FlowState {
|
||||
PostValidation() { this = "PostValidation" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A state signifying if a logical validation has been performed or not.
|
||||
*/
|
||||
private newtype ValidationState =
|
||||
// A state signifying that a logical validation has not been performed.
|
||||
PreValidationState() or
|
||||
// A state signifying that a logical validation has been performed.
|
||||
PostValidationState()
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*
|
||||
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
|
||||
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
|
||||
* DEPRECATED: Use `UnicodeBypassValidationFlow`
|
||||
*/
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnicodeBypassValidation" }
|
||||
|
||||
private ValidationState convertState(DataFlow::FlowState state) {
|
||||
state instanceof PreValidation and result = PreValidationState()
|
||||
or
|
||||
state instanceof PostValidation and result = PostValidationState()
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
UnicodeBypassValidationConfig::isSource(source, this.convertState(state))
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(
|
||||
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
|
||||
DataFlow::FlowState stateTo
|
||||
) {
|
||||
UnicodeBypassValidationConfig::isAdditionalFlowStep(nodeFrom, this.convertState(stateFrom),
|
||||
nodeTo, this.convertState(stateTo))
|
||||
}
|
||||
|
||||
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
|
||||
UnicodeBypassValidationConfig::isSink(sink, this.convertState(state))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*
|
||||
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
|
||||
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnicodeBypassValidation" }
|
||||
private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = ValidationState;
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
source instanceof RemoteFlowSource and state instanceof PreValidation
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof RemoteFlowSource and state = PreValidationState()
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(
|
||||
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
|
||||
DataFlow::FlowState stateTo
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
|
||||
) {
|
||||
(
|
||||
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
|
||||
@@ -75,12 +123,12 @@ class Configuration extends TaintTracking::Configuration {
|
||||
nodeTo = cn
|
||||
)
|
||||
) and
|
||||
stateFrom instanceof PreValidation and
|
||||
stateTo instanceof PostValidation
|
||||
stateFrom = PreValidationState() and
|
||||
stateTo = PostValidationState()
|
||||
}
|
||||
|
||||
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
(
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() = "unicode_normalize" and
|
||||
@@ -118,6 +166,11 @@ class Configuration extends TaintTracking::Configuration {
|
||||
sink = cn.getArgument(0)
|
||||
)
|
||||
) and
|
||||
state instanceof PostValidation
|
||||
state = PostValidationState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*/
|
||||
module UnicodeBypassValidationFlow = TaintTracking::GlobalWithState<UnicodeBypassValidationConfig>;
|
||||
|
||||
@@ -12,8 +12,9 @@ private import codeql.ruby.ApiGraphs
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about zip slip
|
||||
* vulnerabilities.
|
||||
* DEPRECATED: Use `ZipSlipFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ZipSlip" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ZipSlip::Source }
|
||||
@@ -36,3 +37,30 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof ZipSlip::Sanitizer }
|
||||
}
|
||||
|
||||
private module ZipSlipConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ZipSlip::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof ZipSlip::Sink }
|
||||
|
||||
/**
|
||||
* This should actually be
|
||||
* `and cn = API::getTopLevelMember("Gem").getMember("Package").getMember("TarReader").getMember("Entry").getAMethodCall("full_name")` and similar for other classes
|
||||
* but I couldn't make it work so there's only check for the method name called on the entry. It is `full_name` for `Gem::Package::TarReader::Entry` and `Zlib`
|
||||
* and `name` for `Zip::File`
|
||||
*/
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getReceiver() = nodeFrom and
|
||||
cn.getMethodName() in ["full_name", "name"] and
|
||||
cn = nodeTo
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof ZipSlip::Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about zip slip vulnerabilities.
|
||||
*/
|
||||
module ZipSlipFlow = TaintTracking::Global<ZipSlipConfig>;
|
||||
|
||||
@@ -201,11 +201,8 @@ private predicate sqlFragmentArgumentInner(DataFlow::CallNode call, DataFlow::No
|
||||
}
|
||||
|
||||
private predicate sqlFragmentArgument(DataFlow::CallNode call, DataFlow::Node sink) {
|
||||
exists(DataFlow::Node arg |
|
||||
sqlFragmentArgumentInner(call, arg) and
|
||||
sink = [arg, arg.(DataFlow::ArrayLiteralNode).getElement(0)] and
|
||||
unsafeSqlExpr(sink.asExpr().getExpr())
|
||||
)
|
||||
sqlFragmentArgumentInner(call, sink) and
|
||||
unsafeSqlExpr(sink.asExpr().getExpr())
|
||||
}
|
||||
|
||||
// An expression that, if tainted by unsanitized input, should not be used as
|
||||
|
||||
@@ -179,6 +179,12 @@ module Sinatra {
|
||||
}
|
||||
}
|
||||
|
||||
bindingset[local]
|
||||
pragma[inline_late]
|
||||
private predicate isPairKey(string local) {
|
||||
local = any(Pair p).getKey().getConstantValue().getStringlikeValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary for accessing a local variable in an ERB template.
|
||||
* This is the second half of the modeling of the flow from the `locals`
|
||||
@@ -192,7 +198,7 @@ module Sinatra {
|
||||
ErbLocalsAccessSummary() {
|
||||
this = "sinatra_erb_locals_access()" + global.getId() + "#" + local and
|
||||
local = any(MethodCall c | c.getLocation().getFile() = global.getErbFile()).getMethodName() and
|
||||
local = any(Pair p).getKey().getConstantValue().getStringlikeValue()
|
||||
isPairKey(local)
|
||||
}
|
||||
|
||||
override MethodCall getACall() {
|
||||
@@ -273,39 +279,19 @@ module Sinatra {
|
||||
filter.getApp() = route.getApp() and
|
||||
// the filter applies to all routes
|
||||
not filter.hasPattern() and
|
||||
selfPostUpdate(pred, filter.getApp(), filter.getBody().asExpr().getExpr()) and
|
||||
blockCapturedSelfParameterNode(succ, route.getBody().asExpr().getExpr())
|
||||
blockPostUpdate(pred, filter.getBody()) and
|
||||
blockSelfParameterNode(succ, route.getBody().asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a post-update node for the `self` parameter of `app` in block `b`.
|
||||
*
|
||||
* In this example, `n` is the post-update node for `@foo = 1`.
|
||||
* ```rb
|
||||
* class MyApp < Sinatra::Base
|
||||
* before do
|
||||
* @foo = 1
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private predicate selfPostUpdate(DataFlow::PostUpdateNode n, App app, Block b) {
|
||||
n.getPreUpdateNode().asExpr().getExpr() =
|
||||
any(SelfVariableAccess self |
|
||||
pragma[only_bind_into](b) = self.getEnclosingCallable() and
|
||||
self.getVariable().getDeclaringScope() = app.getADeclaration()
|
||||
)
|
||||
/** Holds if `n` is a post-update node for the block `b`. */
|
||||
private predicate blockPostUpdate(DataFlow::PostUpdateNode n, DataFlow::BlockNode b) {
|
||||
n.getPreUpdateNode() = b
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a node representing the `self` parameter captured by block `b`.
|
||||
*/
|
||||
private predicate blockCapturedSelfParameterNode(DataFlow::Node n, Block b) {
|
||||
exists(Ssa::CapturedSelfDefinition d |
|
||||
n.(DataFlowPrivate::SsaDefinitionExtNode).getDefinitionExt() = d and
|
||||
d.getBasicBlock().getScope() = b
|
||||
)
|
||||
/** Holds if `n` is a `self` parameter belonging to block `b`. */
|
||||
private predicate blockSelfParameterNode(DataFlowPrivate::LambdaSelfReferenceNode n, Block b) {
|
||||
n.getCallable() = b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +47,11 @@ module Array {
|
||||
override MethodCall getACallSimple() { result = getAStaticArrayCall("[]") }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
exists(ArrayIndex i |
|
||||
input = "Argument[" + i + "]" and
|
||||
output = "ReturnValue.Element[" + i + "]" and
|
||||
preservesValue = true
|
||||
)
|
||||
// we make use of the special `splat` argument kind, which contains all positional
|
||||
// arguments wrapped in an implicit array, as well as explicit splat arguments
|
||||
input = "Argument[splat]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,9 +210,28 @@ module Array {
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isKnownRange(RangeLiteral rl, int start, int end) {
|
||||
(
|
||||
// Either an explicit, positive beginning index...
|
||||
start = rl.getBegin().getConstantValue().getInt() and start >= 0
|
||||
or
|
||||
// Or a begin-less one, since `..n` is equivalent to `0..n`
|
||||
not exists(rl.getBegin()) and start = 0
|
||||
) and
|
||||
// There must be an explicit end. An end-less range like `2..` is not
|
||||
// treated as a known range, since we don't track the length of the array.
|
||||
exists(int e | e = rl.getEnd().getConstantValue().getInt() and e >= 0 |
|
||||
rl.isInclusive() and end = e
|
||||
or
|
||||
rl.isExclusive() and end = e - 1
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `[]` with an unknown argument, which could be either an index or
|
||||
* a range.
|
||||
* a range. To avoid spurious flow, we are going to ignore the possibility
|
||||
* that the argument might be a range (unless it is an explicit range literal,
|
||||
* see `ElementReferenceRangeReadUnknownSummary`).
|
||||
*/
|
||||
private class ElementReferenceReadUnknownSummary extends ElementReferenceReadSummary {
|
||||
ElementReferenceReadUnknownSummary() {
|
||||
@@ -223,7 +242,7 @@ module Array {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = ["ReturnValue", "ReturnValue.Element[?]"] and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -242,24 +261,8 @@ module Array {
|
||||
)
|
||||
or
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
exists(RangeLiteral rl |
|
||||
rl = mc.getArgument(0) and
|
||||
(
|
||||
// Either an explicit, positive beginning index...
|
||||
start = rl.getBegin().getConstantValue().getInt() and start >= 0
|
||||
or
|
||||
// Or a begin-less one, since `..n` is equivalent to `0..n`
|
||||
not exists(rl.getBegin()) and start = 0
|
||||
) and
|
||||
// There must be an explicit end. An end-less range like `2..` is not
|
||||
// treated as a known range, since we don't track the length of the array.
|
||||
exists(int e | e = rl.getEnd().getConstantValue().getInt() and e >= 0 |
|
||||
rl.isInclusive() and end = e
|
||||
or
|
||||
rl.isExclusive() and end = e - 1
|
||||
) and
|
||||
this = methodName + "(" + start + ".." + end + ")"
|
||||
)
|
||||
isKnownRange(mc.getArgument(0), start, end) and
|
||||
this = methodName + "(" + start + ".." + end + ")"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
@@ -291,12 +294,7 @@ module Array {
|
||||
)
|
||||
or
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
exists(RangeLiteral rl | rl = mc.getArgument(0) |
|
||||
exists(rl.getBegin()) and
|
||||
not exists(int b | b = rl.getBegin().getConstantValue().getInt() and b >= 0)
|
||||
or
|
||||
not exists(int e | e = rl.getEnd().getConstantValue().getInt() and e >= 0)
|
||||
)
|
||||
mc.getArgument(0) = any(RangeLiteral range | not isKnownRange(range, _, _))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ module Hash {
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// we make use of the special `hash-splat` argument kind, which contains all keyword
|
||||
// arguments wrapped in an implicit hash, as well as explicit hash splat arguments
|
||||
input = "Argument[hash-splat].WithElement[any]" and
|
||||
input = "Argument[hash-splat]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string a
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
(
|
||||
argument = ["self", "block", "any", "any-named"]
|
||||
argument = ["self", "lambda-self", "block", "any", "any-named"]
|
||||
or
|
||||
argument.regexpMatch("\\w+:") // keyword argument
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Excon`.
|
||||
@@ -72,8 +71,7 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(ExconDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
ExconDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
or
|
||||
// We set `Excon.defaults[:ssl_verify_peer]` or `Excon.ssl_verify_peer` = false`
|
||||
@@ -114,17 +112,13 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for Excon. */
|
||||
private class ExconDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
ExconDisablesCertificateValidationConfiguration() {
|
||||
this = "ExconDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
private module ExconDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source.asExpr().getExpr().(BooleanLiteral).isFalse() }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(ExconHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module ExconDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<ExconDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -7,7 +7,6 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Faraday`.
|
||||
@@ -78,8 +77,7 @@ class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNod
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(FaradayDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
FaradayDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue(_)
|
||||
}
|
||||
|
||||
@@ -87,15 +85,10 @@ class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNod
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for Faraday. */
|
||||
private class FaradayDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
FaradayDisablesCertificateValidationConfiguration() {
|
||||
this = "FaradayDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
private module FaradayDisablesCertificateValidationConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = string;
|
||||
|
||||
override predicate isSource(
|
||||
DataFlow::Node source, DataFlowImplForHttpClientLibraries::FlowState state
|
||||
) {
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
state = "verify"
|
||||
or
|
||||
@@ -103,7 +96,10 @@ private class FaradayDisablesCertificateValidationConfiguration extends DataFlow
|
||||
state = "verify_mode"
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlowImplForHttpClientLibraries::FlowState state) {
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink = any(FaradayHttpRequest req).getCertificateValidationControllingValue(state)
|
||||
}
|
||||
}
|
||||
|
||||
private module FaradayDisablesCertificateValidationFlow =
|
||||
DataFlow::GlobalWithState<FaradayDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -6,7 +6,6 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `HTTPClient`.
|
||||
@@ -65,8 +64,7 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(HttpClientDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
HttpClientDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -74,17 +72,15 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for HttpClient. */
|
||||
private class HttpClientDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
HttpClientDisablesCertificateValidationConfiguration() {
|
||||
this = "HttpClientDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
private module HttpClientDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(HttpClientRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module HttpClientDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<HttpClientDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -7,7 +7,6 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `HTTParty`.
|
||||
@@ -57,8 +56,7 @@ class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(HttpartyDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
HttpartyDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -66,17 +64,13 @@ class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for Httparty. */
|
||||
private class HttpartyDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
HttpartyDisablesCertificateValidationConfiguration() {
|
||||
this = "HttpartyDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
private module HttpartyDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source.asExpr().getExpr().(BooleanLiteral).isFalse() }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(HttpartyRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module HttpartyDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<HttpartyDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -8,7 +8,6 @@ private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPublic
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A `Net::HTTP` call which initiates an HTTP request.
|
||||
@@ -88,8 +87,7 @@ class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(NetHttpDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
NetHttpDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -97,17 +95,15 @@ class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for NetHttp. */
|
||||
private class NetHttpDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
NetHttpDisablesCertificateValidationConfiguration() {
|
||||
this = "NetHttpDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
private module NetHttpDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(NetHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module NetHttpDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<NetHttpDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -8,7 +8,6 @@ private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.Core
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `OpenURI` via `URI.open` or
|
||||
@@ -46,8 +45,7 @@ class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(OpenUriDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
OpenUriDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -94,8 +92,7 @@ class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::C
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(OpenUriDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
OpenUriDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -103,19 +100,17 @@ class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::C
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for OpenURI. */
|
||||
private class OpenUriDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
OpenUriDisablesCertificateValidationConfiguration() {
|
||||
this = "OpenUriDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
private module OpenUriDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(OpenUriRequest req).getCertificateValidationControllingValue()
|
||||
or
|
||||
sink = any(OpenUriKernelOpenRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module OpenUriDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<OpenUriDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -7,7 +7,6 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `RestClient`.
|
||||
@@ -58,8 +57,7 @@ class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::Call
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(RestClientDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
RestClientDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -67,17 +65,15 @@ class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::Call
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for RestClient. */
|
||||
private class RestClientDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
RestClientDisablesCertificateValidationConfiguration() {
|
||||
this = "RestClientDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
private module RestClientDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(RestClientHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module RestClientDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<RestClientDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -7,7 +7,6 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Typhoeus`.
|
||||
@@ -38,8 +37,7 @@ class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNo
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(TyphoeusDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
TyphoeusDisablesCertificateValidationFlow::flow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
@@ -47,17 +45,13 @@ class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNo
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for Typhoeus. */
|
||||
private class TyphoeusDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration
|
||||
{
|
||||
TyphoeusDisablesCertificateValidationConfiguration() {
|
||||
this = "TyphoeusDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
private module TyphoeusDisablesCertificateValidationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source.asExpr().getExpr().(BooleanLiteral).isFalse() }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(TyphoeusHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
private module TyphoeusDisablesCertificateValidationFlow =
|
||||
DataFlow::Global<TyphoeusDisablesCertificateValidationConfig>;
|
||||
|
||||
@@ -5,7 +5,6 @@ private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForPathname
|
||||
|
||||
/**
|
||||
* Modeling of the `Pathname` class from the Ruby standard library.
|
||||
@@ -28,13 +27,11 @@ module Pathname {
|
||||
*/
|
||||
class PathnameInstance extends FileNameSource {
|
||||
cached
|
||||
PathnameInstance() { any(PathnameConfiguration c).hasFlowTo(this) }
|
||||
PathnameInstance() { PathnameFlow::flowTo(this) }
|
||||
}
|
||||
|
||||
private class PathnameConfiguration extends Configuration {
|
||||
PathnameConfiguration() { this = "PathnameConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
private module PathnameConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
// A call to `Pathname.new`.
|
||||
source = API::getTopLevelMember("Pathname").getAnInstantiation()
|
||||
or
|
||||
@@ -42,9 +39,9 @@ module Pathname {
|
||||
source = API::getTopLevelMember("Pathname").getAMethodCall(["getwd", "pwd",])
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { any() }
|
||||
predicate isSink(DataFlow::Node sink) { any() }
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
node2 =
|
||||
any(DataFlow::CallNode c |
|
||||
c.getReceiver() = node1 and
|
||||
@@ -57,6 +54,8 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
private module PathnameFlow = DataFlow::Global<PathnameConfig>;
|
||||
|
||||
/** A call where the receiver is a `Pathname`. */
|
||||
class PathnameCall extends DataFlow::CallNode {
|
||||
PathnameCall() { this.getReceiver() instanceof PathnameInstance }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides a taint-tracking configuration for "Clear-text logging of sensitive information".
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CleartextLogging::Configuration` is needed, otherwise
|
||||
* `CleartextLoggingFlow` is needed, otherwise
|
||||
* `CleartextLoggingCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -10,25 +10,43 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
import CleartextLoggingCustomizations::CleartextLogging
|
||||
private import CleartextLoggingCustomizations::CleartextLogging as CleartextLogging
|
||||
private import CleartextLoggingCustomizations::CleartextLogging as CL
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Clear-text logging of sensitive information".
|
||||
* DEPRECATED: Use `CleartextLoggingFlow` instead
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CleartextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof CleartextLogging::Source }
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof CL::Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextLogging::Sink }
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CL::Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node)
|
||||
or
|
||||
node instanceof CleartextLogging::Sanitizer
|
||||
node instanceof CL::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
CleartextLogging::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof CL::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof CL::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof CL::Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "Clear-text logging of sensitive information".
|
||||
*/
|
||||
module CleartextLoggingFlow = TaintTracking::Global<Config>;
|
||||
|
||||
@@ -2,32 +2,50 @@
|
||||
* Provides a taint-tracking configuration for "Clear-text storage of sensitive information".
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `Configuration` is needed, otherwise `CleartextStorageCustomizations` should be
|
||||
* imported instead.
|
||||
* `CleartextStorageFlow` is needed, otherwise
|
||||
* `CleartextStorageCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import CleartextStorageCustomizations::CleartextStorage as CleartextStorage
|
||||
private import CleartextStorageCustomizations::CleartextStorage as CS
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Clear-text storage of sensitive information".
|
||||
* DEPRECATED: Use `CleartextStorageFlow` instead
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CleartextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof CleartextStorage::Source }
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof CS::Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextStorage::Sink }
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CS::Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node)
|
||||
or
|
||||
node instanceof CleartextStorage::Sanitizer
|
||||
node instanceof CS::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
CleartextStorage::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof CS::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof CS::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof CS::Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "Clear-text storage of sensitive information".
|
||||
*/
|
||||
module CleartextStorageFlow = TaintTracking::Global<Config>;
|
||||
|
||||
@@ -13,27 +13,82 @@ private import codeql.ruby.dataflow.BarrierGuards
|
||||
module CodeInjection {
|
||||
/** Flow states used to distinguish whether an attacker controls the entire string. */
|
||||
module FlowState {
|
||||
/** Flow state used for normal tainted data, where an attacker might only control a substring. */
|
||||
DataFlow::FlowState substring() { result = "substring" }
|
||||
/**
|
||||
* Flow state used for normal tainted data, where an attacker might only control a substring.
|
||||
* DEPRECATED: Use `Full()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState substring() { result = "substring" }
|
||||
|
||||
/** Flow state used for data that is entirely controlled by the attacker. */
|
||||
DataFlow::FlowState full() { result = "full" }
|
||||
/**
|
||||
* Flow state used for data that is entirely controlled by the attacker.
|
||||
* DEPRECATED: Use `Full()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState full() { result = "full" }
|
||||
|
||||
private newtype TState =
|
||||
TFull() or
|
||||
TSubString()
|
||||
|
||||
/** A flow state used to distinguish whether an attacker controls the entire string. */
|
||||
class State extends TState {
|
||||
/**
|
||||
* Gets a string representation of this state.
|
||||
*/
|
||||
string toString() { result = this.getStringRepresentation() }
|
||||
|
||||
/**
|
||||
* Gets a canonical string representation of this state.
|
||||
*/
|
||||
string getStringRepresentation() {
|
||||
this = TSubString() and result = "substring"
|
||||
or
|
||||
this = TFull() and result = "full"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state used for normal tainted data, where an attacker might only control a substring.
|
||||
*/
|
||||
class SubString extends State, TSubString { }
|
||||
|
||||
/**
|
||||
* A flow state used for data that is entirely controlled by the attacker.
|
||||
*/
|
||||
class Full extends State, TFull { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source for "Code injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a flow state for which this is a source.
|
||||
* DEPRECATED: Use `getAState()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState getAFlowState() {
|
||||
result = [FlowState::substring(), FlowState::full()]
|
||||
}
|
||||
|
||||
/** Gets a flow state for which this is a source. */
|
||||
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
|
||||
FlowState::State getAState() {
|
||||
result instanceof FlowState::SubString or result instanceof FlowState::Full
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for "Code injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if this sink is safe for an attacker that only controls a substring.
|
||||
* DEPRECATED: Use `getAState()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState getAFlowState() {
|
||||
result = [FlowState::substring(), FlowState::full()]
|
||||
}
|
||||
|
||||
/** Holds if this sink is safe for an attacker that only controls a substring. */
|
||||
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
|
||||
FlowState::State getAState() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +98,15 @@ module CodeInjection {
|
||||
/**
|
||||
* Gets a flow state for which this is a sanitizer.
|
||||
* Sanitizes all states if the result is empty.
|
||||
* DEPRECATED: Use `getAState()`
|
||||
*/
|
||||
DataFlow::FlowState getAFlowState() { none() }
|
||||
deprecated DataFlow::FlowState getAFlowState() { none() }
|
||||
|
||||
/**
|
||||
* Gets a flow state for which this is a sanitizer.
|
||||
* Sanitizes all states if the result is empty.
|
||||
*/
|
||||
FlowState::State getAState() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,12 +129,17 @@ module CodeInjection {
|
||||
|
||||
CodeExecutionAsSink() { this = c.getCode() }
|
||||
|
||||
/** Gets a flow state for which this is a sink. */
|
||||
override DataFlow::FlowState getAFlowState() {
|
||||
deprecated override DataFlow::FlowState getAFlowState() {
|
||||
if c.runsArbitraryCode()
|
||||
then result = [FlowState::substring(), FlowState::full()] // If it runs arbitrary code then it's always vulnerable.
|
||||
else result = FlowState::full() // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
|
||||
}
|
||||
|
||||
override FlowState::State getAState() {
|
||||
if c.runsArbitraryCode()
|
||||
then any() // If it runs arbitrary code then it's always vulnerable.
|
||||
else result instanceof FlowState::Full // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
|
||||
}
|
||||
}
|
||||
|
||||
private import codeql.ruby.AST as Ast
|
||||
@@ -92,6 +159,8 @@ module CodeInjection {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
|
||||
deprecated override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
|
||||
|
||||
override FlowState::State getAState() { result instanceof FlowState::Full }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Code injection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `CodeInjectionCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CodeInjectionFlow` is needed, otherwise
|
||||
* `CodeInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
@@ -12,8 +13,9 @@ import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Code injection" vulnerabilities.
|
||||
* DEPRECATED: Use `CodeInjectionFlow` instead
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CodeInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
@@ -40,3 +42,26 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::StateConfigSig {
|
||||
class FlowState = FlowState::State;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) { state = source.(Source).getAState() }
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) { state = sink.(Sink).getAState() }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer and not exists(node.(Sanitizer).getAState())
|
||||
or
|
||||
node instanceof StringConstCompareBarrier
|
||||
or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) { node.(Sanitizer).getAState() = state }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "Code injection" vulnerabilities.
|
||||
*/
|
||||
module CodeInjectionFlow = TaintTracking::GlobalWithState<Config>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* command-injection vulnerabilities (CWE-078).
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CommandInjection::Configuration` is needed, otherwise
|
||||
* `CommandInjectionFlow` is needed, otherwise
|
||||
* `CommandInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -15,8 +15,9 @@ import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about command-injection vulnerabilities.
|
||||
* DEPRECATED: Use `CommandInjectionFlow` instead
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CommandInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -29,3 +30,20 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer or
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about command-injection vulnerabilities.
|
||||
*/
|
||||
module CommandInjectionFlow = TaintTracking::Global<Config>;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides a taint tracking configuration for reasoning about bypass of sensitive action guards.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `ConditionalBypass::Configuration` is needed, otherwise
|
||||
* `ConditionalBypassFlow` is needed, otherwise
|
||||
* `ConditionalBypassCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -13,8 +13,9 @@ import ConditionalBypassCustomizations::ConditionalBypass
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for bypass of sensitive action guards.
|
||||
* DEPRECATED: Use `ConditionalBypassFlow` instead
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ConditionalBypass" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -26,3 +27,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for bypass of sensitive action guards.
|
||||
*/
|
||||
module ConditionalBypassFlow = TaintTracking::Global<Config>;
|
||||
|
||||
@@ -19,19 +19,42 @@ module HardcodedDataInterpretedAsCode {
|
||||
* Flow states used to distinguish value-preserving flow from taint flow.
|
||||
*/
|
||||
module FlowState {
|
||||
/** Flow state used to track value-preserving flow. */
|
||||
DataFlow::FlowState data() { result = "data" }
|
||||
/**
|
||||
* Flow state used to track value-preserving flow.
|
||||
* DEPRECATED: Use `Data()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState data() { result = "data" }
|
||||
|
||||
/** Flow state used to tainted data (non-value preserving flow). */
|
||||
DataFlow::FlowState taint() { result = "taint" }
|
||||
/**
|
||||
* Flow state used to tainted data (non-value preserving flow).
|
||||
* DEPRECATED: Use `Taint()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState taint() { result = "taint" }
|
||||
|
||||
/**
|
||||
* Flow states used to distinguish value-preserving flow from taint flow.
|
||||
*/
|
||||
newtype State =
|
||||
/** Flow state used to track value-preserving flow. */
|
||||
Data() or
|
||||
/** Flow state used to tainted data (non-value preserving flow). */
|
||||
Taint()
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source for hard-coded data.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/** Gets a flow label for which this is a source. */
|
||||
DataFlow::FlowState getLabel() { result = FlowState::data() }
|
||||
/**
|
||||
* Gets a flow label for which this is a source.
|
||||
* DEPRECATED: Use `getALabel()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState getLabel() { result = FlowState::data() }
|
||||
|
||||
/**
|
||||
* Gets a flow label for which this is a source.
|
||||
*/
|
||||
FlowState::State getALabel() { result = FlowState::Data() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,13 +64,26 @@ module HardcodedDataInterpretedAsCode {
|
||||
/** Gets a description of what kind of sink this is. */
|
||||
abstract string getKind();
|
||||
|
||||
/** Gets a flow label for which this is a sink. */
|
||||
DataFlow::FlowState getLabel() {
|
||||
/**
|
||||
* Gets a flow label for which this is a sink.
|
||||
* DEPRECATED: Use `getALabel()`
|
||||
*/
|
||||
deprecated DataFlow::FlowState getLabel() {
|
||||
// We want to ignore value-flow and only consider taint-flow, since the
|
||||
// source is just a hex string, and evaluating that directly will just
|
||||
// cause a syntax error.
|
||||
result = FlowState::taint()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a flow label for which this is a sink.
|
||||
*/
|
||||
FlowState::State getALabel() {
|
||||
// We want to ignore value-flow and only consider taint-flow, since the
|
||||
// source is just a hex string, and evaluating that directly will just
|
||||
// cause a syntax error.
|
||||
result = FlowState::Taint()
|
||||
}
|
||||
}
|
||||
|
||||
/** A sanitizer for hard-coded data. */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* being interpreted as code.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `HardcodedDataInterpretedAsCode::Configuration` is needed, otherwise
|
||||
* `HardcodedDataInterpretedAsCodeFlow` is needed, otherwise
|
||||
* `HardcodedDataInterpretedAsCodeCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -16,11 +16,9 @@ import HardcodedDataInterpretedAsCodeCustomizations::HardcodedDataInterpretedAsC
|
||||
* A taint-tracking configuration for reasoning about hard-coded data
|
||||
* being interpreted as code.
|
||||
*
|
||||
* We extend `DataFlow::Configuration` rather than
|
||||
* `TaintTracking::Configuration`, so that we can set the flow state to
|
||||
* `"taint"` on a taint step.
|
||||
* DEPRECATED: Use `HardcodedDataInterpretedAsCodeFlow` instead
|
||||
*/
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
deprecated class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "HardcodedDataInterpretedAsCode" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState label) {
|
||||
@@ -46,3 +44,34 @@ class Configuration extends DataFlow::Configuration {
|
||||
stateTo = FlowState::taint()
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::StateConfigSig {
|
||||
class FlowState = FlowState::State;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState label) { source.(Source).getALabel() = label }
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState label) { sink.(Sink).getALabel() = label }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
|
||||
) {
|
||||
defaultAdditionalTaintStep(nodeFrom, nodeTo) and
|
||||
// This is a taint step, so the flow state becomes `taint`.
|
||||
(
|
||||
stateFrom = FlowState::Taint()
|
||||
or
|
||||
stateFrom = FlowState::Data()
|
||||
) and
|
||||
stateTo = FlowState::Taint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about hard-coded data being interpreted as code.
|
||||
* We implement `DataFlow::GlobalWithState` rather than
|
||||
* `TaintTracking::GlobalWithState`, so that we can set the flow state to
|
||||
* `Taint()` on a taint step.
|
||||
*/
|
||||
module HardcodedDataInterpretedAsCodeFlow = DataFlow::GlobalWithState<Config>;
|
||||
|
||||
@@ -9,8 +9,9 @@ private import ImproperLdapAuthCustomizations::ImproperLdapAuth
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting improper LDAP authentication vulnerabilities.
|
||||
* DEPRECATED: Use `ImproperLdapAuthFlow` instead
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ImproperLdapAuth" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -19,3 +20,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module ImproperLdapAuthConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting improper LDAP authentication vulnerabilities.
|
||||
*/
|
||||
module ImproperLdapAuthFlow = TaintTracking::Global<ImproperLdapAuthConfig>;
|
||||
|
||||
@@ -21,8 +21,14 @@ module InsecureDownload {
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a flow-label for this source.
|
||||
* DEPRECATED: Use `getAFlowLabel()`
|
||||
*/
|
||||
abstract DataFlow::FlowState getALabel();
|
||||
abstract deprecated DataFlow::FlowState getALabel();
|
||||
|
||||
/**
|
||||
* Gets a flow-label for this source.
|
||||
*/
|
||||
abstract Label::State getAFlowLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,8 +42,14 @@ module InsecureDownload {
|
||||
|
||||
/**
|
||||
* Gets a flow-label where this sink is vulnerable.
|
||||
* DEPRECATED: Use `getAFlowLabel()`
|
||||
*/
|
||||
abstract DataFlow::FlowState getALabel();
|
||||
abstract deprecated DataFlow::FlowState getALabel();
|
||||
|
||||
/**
|
||||
* Gets a flow-label where this sink is vulnerable.
|
||||
*/
|
||||
abstract Label::State getAFlowLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,24 +63,38 @@ module InsecureDownload {
|
||||
module Label {
|
||||
/**
|
||||
* A flow-label for a URL that is downloaded over an insecure connection.
|
||||
* DEPRECATED: Use `InsecureState()`
|
||||
*/
|
||||
class Insecure extends DataFlow::FlowState {
|
||||
deprecated class Insecure extends DataFlow::FlowState {
|
||||
Insecure() { this = "insecure" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow-label for a URL that is sensitive.
|
||||
* DEPRECATED: Use `SensitiveState()`
|
||||
*/
|
||||
class Sensitive extends DataFlow::FlowState {
|
||||
deprecated class Sensitive extends DataFlow::FlowState {
|
||||
Sensitive() { this = "sensitive" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow-label for file URLs that are both sensitive and downloaded over an insecure connection.
|
||||
* DEPRECATED: Use `SensitiveInsecureState()`
|
||||
*/
|
||||
class SensitiveInsecure extends DataFlow::FlowState {
|
||||
deprecated class SensitiveInsecure extends DataFlow::FlowState {
|
||||
SensitiveInsecure() { this = "sensitiveInsecure" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow-labels for reasoning about download of sensitive file through insecure connection.
|
||||
*/
|
||||
newtype State =
|
||||
/** A flow-label for a URL that is downloaded over an insecure connection. */
|
||||
InsecureState() or
|
||||
/** A flow-label for a URL that is sensitive. */
|
||||
SensitiveState() or
|
||||
/** A flow-label for file URLs that are both sensitive and downloaded over an insecure connection. */
|
||||
SensitiveInsecureState()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,12 +114,19 @@ module InsecureDownload {
|
||||
* seen as a source for downloads of sensitive files through an insecure connection.
|
||||
*/
|
||||
class InsecureFileUrl extends Source, InsecureUrl {
|
||||
override DataFlow::FlowState getALabel() {
|
||||
deprecated override DataFlow::FlowState getALabel() {
|
||||
result instanceof Label::Insecure
|
||||
or
|
||||
hasUnsafeExtension(str) and
|
||||
result instanceof Label::SensitiveInsecure
|
||||
}
|
||||
|
||||
override Label::State getAFlowLabel() {
|
||||
result = Label::InsecureState()
|
||||
or
|
||||
hasUnsafeExtension(str) and
|
||||
result = Label::SensitiveInsecureState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +136,9 @@ module InsecureDownload {
|
||||
class SensitiveFileName extends Source {
|
||||
SensitiveFileName() { hasUnsafeExtension(this.asExpr().getConstantValue().getString()) }
|
||||
|
||||
override DataFlow::FlowState getALabel() { result instanceof Label::Sensitive }
|
||||
deprecated override DataFlow::FlowState getALabel() { result instanceof Label::Sensitive }
|
||||
|
||||
override Label::State getAFlowLabel() { result = Label::SensitiveState() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,11 +180,17 @@ module InsecureDownload {
|
||||
|
||||
override DataFlow::Node getDownloadCall() { result = req }
|
||||
|
||||
override DataFlow::FlowState getALabel() {
|
||||
deprecated override DataFlow::FlowState getALabel() {
|
||||
result instanceof Label::SensitiveInsecure
|
||||
or
|
||||
any(req.getAUrlPart()) instanceof InsecureUrl and result instanceof Label::Sensitive
|
||||
}
|
||||
|
||||
override Label::State getAFlowLabel() {
|
||||
result = Label::SensitiveInsecureState()
|
||||
or
|
||||
any(req.getAUrlPart()) instanceof InsecureUrl and result = Label::SensitiveState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,7 +232,9 @@ module InsecureDownload {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::FlowState getALabel() { result instanceof Label::Insecure }
|
||||
deprecated override DataFlow::FlowState getALabel() { result instanceof Label::Insecure }
|
||||
|
||||
override Label::State getAFlowLabel() { result = Label::InsecureState() }
|
||||
|
||||
override DataFlow::Node getDownloadCall() { result = request }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides a dataflow configuration for reasoning about the download of sensitive file through insecure connection.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `InsecureDownload::Configuration` is needed, otherwise
|
||||
* `InsecureDownloadFlow` is needed, otherwise
|
||||
* `InsecureDownloadCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,8 @@ import InsecureDownloadCustomizations::InsecureDownload
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for download of sensitive file through insecure connection.
|
||||
*
|
||||
* DEPRECATED: Use `InsecureDownloadFlow`.
|
||||
*/
|
||||
deprecated class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "InsecureDownload" }
|
||||
@@ -30,21 +32,25 @@ deprecated class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for download of sensitive file through insecure connection.
|
||||
*/
|
||||
module Config implements DataFlow::StateConfigSig {
|
||||
class FlowState = string;
|
||||
private module InsecureDownloadConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = Label::State;
|
||||
|
||||
predicate isSource(DataFlow::Node source, DataFlow::FlowState label) {
|
||||
source.(Source).getALabel() = label
|
||||
predicate isSource(DataFlow::Node source, FlowState label) {
|
||||
source.(Source).getAFlowLabel() = label
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, DataFlow::FlowState label) {
|
||||
sink.(Sink).getALabel() = label
|
||||
}
|
||||
predicate isSink(DataFlow::Node sink, FlowState label) { sink.(Sink).getAFlowLabel() = label }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
module Flow = DataFlow::GlobalWithState<Config>;
|
||||
/**
|
||||
* Taint-tracking for download of sensitive file through insecure connection.
|
||||
*/
|
||||
module InsecureDownloadFlow = DataFlow::GlobalWithState<InsecureDownloadConfig>;
|
||||
|
||||
/** DEPRECATED: Use `InsecureDownloadConfig` */
|
||||
deprecated module Config = InsecureDownloadConfig;
|
||||
|
||||
/** DEPRECATED: Use `InsecureDownloadFlow` */
|
||||
deprecated module Flow = InsecureDownloadFlow;
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.frameworks.core.Kernel::Kernel
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.TaintTracking
|
||||
|
||||
/** A call to a method that might access a file or start a process. */
|
||||
class AmbiguousPathCall extends DataFlow::CallNode {
|
||||
@@ -72,3 +74,20 @@ abstract class Sanitizer extends DataFlow::Node { }
|
||||
private class FileJoinSanitizer extends Sanitizer {
|
||||
FileJoinSanitizer() { this = any(File::FileJoinSummary s).getParameter("1..") }
|
||||
}
|
||||
|
||||
private module KernelOpenConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink = any(AmbiguousPathCall r).getPathArgument() }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting insecure uses of `Kernel.open` and similar sinks.
|
||||
*/
|
||||
module KernelOpenFlow = TaintTracking::Global<KernelOpenConfig>;
|
||||
|
||||
@@ -5,25 +5,30 @@
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import LdapInjectionCustomizations::LdapInjection as LI
|
||||
|
||||
/** Provides a taint-tracking configuration for detecting LDAP Injections vulnerabilities. */
|
||||
module LdapInjection {
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting LDAP Injections vulnerabilities.
|
||||
* DEPRECATED: Use `LdapInjectionFlow` instead
|
||||
*/
|
||||
deprecated module LdapInjection {
|
||||
import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
LdapInjection::isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
import TaintTracking::Global<Config>
|
||||
import TaintTracking::Global<LdapInjectionConfig>
|
||||
}
|
||||
|
||||
private module LdapInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof LI::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof LI::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof LI::Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
LI::isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting LDAP Injections vulnerabilities.
|
||||
*/
|
||||
module LdapInjectionFlow = TaintTracking::Global<LdapInjectionConfig>;
|
||||
|
||||
@@ -26,8 +26,9 @@ abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for untrusted user input used in log entries.
|
||||
* DEPRECATED: Use `LogInjectionFlow`
|
||||
*/
|
||||
class LogInjectionConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class LogInjectionConfiguration extends TaintTracking::Configuration {
|
||||
LogInjectionConfiguration() { this = "LogInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -74,3 +75,16 @@ class InspectSanitizer extends Sanitizer {
|
||||
class HtmlEscapingAsSanitizer extends Sanitizer {
|
||||
HtmlEscapingAsSanitizer() { this = any(HtmlEscaping esc).getOutput() }
|
||||
}
|
||||
|
||||
private module LogInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for untrusted user input used in log entries.
|
||||
*/
|
||||
module LogInjectionFlow = TaintTracking::Global<LogInjectionConfig>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* path injection vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `PathInjection::Configuration` is needed, otherwise
|
||||
* `PathInjectionFlow` is needed, otherwise
|
||||
* `PathInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -15,8 +15,9 @@ private import codeql.ruby.TaintTracking
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about path injection
|
||||
* vulnerabilities.
|
||||
* DEPRECATED: Use `PathInjectionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "PathInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof PathInjection::Source }
|
||||
@@ -31,3 +32,18 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof PathInjection::SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module PathInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof PathInjection::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof PathInjection::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Path::PathSanitization or node instanceof PathInjection::Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting path injection vulnerabilities.
|
||||
*/
|
||||
module PathInjectionFlow = TaintTracking::Global<PathInjectionConfig>;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `ReflectedXSS::Configuration` is needed, otherwise
|
||||
* `XSS::ReflectedXSS` should be imported instead.
|
||||
* `ReflectedXssFlow` is needed, otherwise
|
||||
* `XSS::ReflectedXss` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
@@ -12,14 +12,16 @@ import codeql.ruby.TaintTracking
|
||||
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
|
||||
* DEPRECATED: Use `ReflectedXssFlow`
|
||||
*/
|
||||
module ReflectedXss {
|
||||
deprecated module ReflectedXss {
|
||||
import XSS::ReflectedXss
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
|
||||
* DEPRECATED: Use `ReflectedXssFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ReflectedXSS" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -37,3 +39,22 @@ module ReflectedXss {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module ReflectedXssConfig implements DataFlow::ConfigSig {
|
||||
private import XSS::ReflectedXss as RX
|
||||
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RX::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof RX::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof RX::Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
RX::isAdditionalXssTaintStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "reflected server-side cross-site scripting" vulnerabilities.
|
||||
*/
|
||||
module ReflectedXssFlow = TaintTracking::Global<ReflectedXssConfig>;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Provides a taint-tracking configuration for detecting flow of query string
|
||||
* data to sensitive actions in GET query request handlers.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is
|
||||
* needed, otherwise `SensitiveGetQueryCustomizations` should be imported
|
||||
* instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `SensitiveGetQueryFlow` is needed, otherwise
|
||||
* `SensitiveGetQueryCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
@@ -13,15 +13,16 @@ private import codeql.ruby.TaintTracking
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting flow of query string
|
||||
* data to sensitive actions in GET query request handlers.
|
||||
* DEPRECATED: Use `SensitiveGetQueryFlow`
|
||||
*/
|
||||
module SensitiveGetQuery {
|
||||
deprecated module SensitiveGetQuery {
|
||||
import SensitiveGetQueryCustomizations::SensitiveGetQuery
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about use of sensitive data
|
||||
* from a GET request query string.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SensitiveGetQuery" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -29,3 +30,17 @@ module SensitiveGetQuery {
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
}
|
||||
}
|
||||
|
||||
private module SensitiveGetQueryConfig implements DataFlow::ConfigSig {
|
||||
import SensitiveGetQueryCustomizations::SensitiveGetQuery
|
||||
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about use of sensitive data from a
|
||||
* GET request query string.
|
||||
*/
|
||||
module SensitiveGetQueryFlow = TaintTracking::Global<SensitiveGetQueryConfig>;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* Provides a taint-tracking configuration for detecting
|
||||
* "Server side request forgery" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `ServerSideRequestForgeryCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `ServerSideRequestForgeryFlow` is needed, otherwise
|
||||
* `ServerSideRequestForgeryCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
@@ -14,8 +15,9 @@ import codeql.ruby.dataflow.BarrierGuards
|
||||
/**
|
||||
* A taint-tracking configuration for detecting
|
||||
* "Server side request forgery" vulnerabilities.
|
||||
* DEPRECATED: Use `ServerSideRequestForgeryFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ServerSideRequestForgery" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -32,3 +34,20 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module ServerSideRequestForgeryConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer or
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "Server side request forgery" vulnerabilities.
|
||||
*/
|
||||
module ServerSideRequestForgeryFlow = TaintTracking::Global<ServerSideRequestForgeryConfig>;
|
||||
|
||||
@@ -9,8 +9,9 @@ import SqlInjectionCustomizations::SqlInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting SQL injection vulnerabilities.
|
||||
* DEPRECATED: Use `SqlInjectionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SqlInjectionConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -19,3 +20,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module SqlInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting SQL injection vulnerabilities.
|
||||
*/
|
||||
module SqlInjectionFlow = TaintTracking::Global<SqlInjectionConfig>;
|
||||
|
||||
@@ -13,8 +13,9 @@ private import StackTraceExposureCustomizations::StackTraceExposure
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "stack trace exposure" vulnerabilities.
|
||||
* DEPRECATED: Use `StackTraceExposureFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "StackTraceExposure" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -23,3 +24,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module StackTraceExposureConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "stack trace exposure" vulnerabilities.
|
||||
*/
|
||||
module StackTraceExposureFlow = TaintTracking::Global<StackTraceExposureConfig>;
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
* cross-site scripting vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `StoredXSS::Configuration` is needed, otherwise
|
||||
* `XSS::StoredXSS` should be imported instead.
|
||||
* `StoredXssFlow` is needed, otherwise
|
||||
* `XSS::StoredXss` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
|
||||
/** Provides a taint-tracking configuration for cross-site scripting vulnerabilities. */
|
||||
module StoredXss {
|
||||
/**
|
||||
* Provides a taint-tracking configuration for cross-site scripting vulnerabilities.
|
||||
* DEPRECATED: Use StoredXssFlow
|
||||
*/
|
||||
deprecated module StoredXss {
|
||||
import XSS::StoredXss
|
||||
|
||||
/**
|
||||
@@ -41,20 +44,24 @@ module StoredXss {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about Stored XSS.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isAdditionalXssTaintStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
import TaintTracking::Global<Config>
|
||||
import TaintTracking::Global<StoredXssConfig>
|
||||
}
|
||||
|
||||
private module StoredXssConfig implements DataFlow::ConfigSig {
|
||||
private import XSS::StoredXss
|
||||
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isAdditionalXssTaintStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about Stored XSS.
|
||||
*/
|
||||
module StoredXssFlow = TaintTracking::Global<StoredXssConfig>;
|
||||
|
||||
@@ -9,8 +9,9 @@ import TemplateInjectionCustomizations::TemplateInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting Server Side Template Injections vulnerabilities.
|
||||
* DEPRECATED: Use `TemplateInjectionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "TemplateInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -19,3 +20,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module TemplateInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting Server Side Template Injections vulnerabilities.
|
||||
*/
|
||||
module TemplateInjectionFlow = TaintTracking::Global<TemplateInjectionConfig>;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* Provides a taint-tracking configuration for reasoning about code
|
||||
* constructed from library input vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `UnsafeCodeConstructionCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `UnsafeCodeConstructionFlow` is needed, otherwise
|
||||
* `UnsafeCodeConstructionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
@@ -13,8 +14,9 @@ private import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting code constructed from library input vulnerabilities.
|
||||
* DEPRECATED: Use `UnsafeCodeConstructionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeShellCommandConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -30,10 +32,29 @@ class Configuration extends TaintTracking::Configuration {
|
||||
override DataFlow::FlowFeature getAFeature() {
|
||||
result instanceof DataFlow::FeatureHasSourceCallContext
|
||||
}
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
|
||||
private module UnsafeCodeConstructionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
|
||||
// override to require the path doesn't have unmatched return steps
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
|
||||
// allow implicit reads of array elements
|
||||
this.isSink(node) and
|
||||
isSink(node) and
|
||||
set.isElementOfTypeOrUnknown("int")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting code constructed from library input vulnerabilities.
|
||||
*/
|
||||
module UnsafeCodeConstructionFlow = TaintTracking::Global<UnsafeCodeConstructionConfig>;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides a taint-tracking configuration for reasoning about unsafe deserialization.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `UnsafeDeserialization::Configuration` is needed, otherwise
|
||||
* `UnsafeDeserializationFlow` is needed, otherwise
|
||||
* `UnsafeDeserializationCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -13,8 +13,9 @@ import UnsafeDeserializationCustomizations
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about unsafe deserialization.
|
||||
* DEPRECATED: Use `UnsafeDeserializationFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeDeserialization" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
@@ -28,3 +29,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof UnsafeDeserialization::Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private module UnsafeDeserializationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof UnsafeDeserialization::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserialization::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof UnsafeDeserialization::Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about unsafe deserialization.
|
||||
*/
|
||||
module UnsafeCodeConstructionFlow = TaintTracking::Global<UnsafeDeserializationConfig>;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* Provides a taint-tracking configuration for reasoning about HTML
|
||||
* constructed from library input vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `UnsafeHtmlConstructionCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `UnsafeHtmlConstructionFlow` is needed, otherwise
|
||||
* `UnsafeHtmlConstructionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
@@ -13,8 +14,9 @@ private import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting unsafe HTML construction.
|
||||
* DEPRECATED: Use `UnsafeHtmlConstructionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeHtmlConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -31,3 +33,22 @@ class Configuration extends TaintTracking::Configuration {
|
||||
result instanceof DataFlow::FeatureHasSourceCallContext
|
||||
}
|
||||
}
|
||||
|
||||
private module UnsafeHtmlConstructionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
|
||||
// override to require the path doesn't have unmatched return steps
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting unsafe HTML construction.
|
||||
*/
|
||||
module UnsafeHtmlConstructionFlow = TaintTracking::Global<UnsafeHtmlConstructionConfig>;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.DataFlow2
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.core.Gem::Gem as Gem
|
||||
private import codeql.ruby.Concepts as Concepts
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* Provides a taint tracking configuration for reasoning about shell command
|
||||
* constructed from library input vulnerabilities
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `UnsafeShellCommandConstructionCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `UnsafeShellCommandConstructionFlow` is needed, otherwise
|
||||
* `UnsafeShellCommandConstructionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
@@ -14,8 +15,9 @@ private import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting shell command constructed from library input vulnerabilities.
|
||||
* DEPRECATED: Use `UnsafeShellCommandConstructionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeShellCommandConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -32,10 +34,31 @@ class Configuration extends TaintTracking::Configuration {
|
||||
override DataFlow::FlowFeature getAFeature() {
|
||||
result instanceof DataFlow::FeatureHasSourceCallContext
|
||||
}
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
|
||||
private module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof CommandInjection::Sanitizer or // using all sanitizers from `rb/command-injection`
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
|
||||
// override to require the path doesn't have unmatched return steps
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
|
||||
// allow implicit reads of array elements
|
||||
this.isSink(node) and
|
||||
isSink(node) and
|
||||
set.isElementOfTypeOrUnknown("int")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting shell command constructed from library input vulnerabilities.
|
||||
*/
|
||||
module UnsafeShellCommandConstructionFlow =
|
||||
TaintTracking::Global<UnsafeShellCommandConstructionConfig>;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "URL redirection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `UrlRedirectCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `UrlRedirectConfig` is needed, otherwise
|
||||
* `UrlRedirectCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
@@ -13,8 +14,9 @@ import UrlRedirectCustomizations::UrlRedirect
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "URL redirection" vulnerabilities.
|
||||
* DEPRECATED: Use `UrlRedirectFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UrlRedirect" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -31,3 +33,20 @@ class Configuration extends TaintTracking::Configuration {
|
||||
UrlRedirect::isAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
private module UrlRedirectConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
UrlRedirect::isAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "URL redirection" vulnerabilities.
|
||||
*/
|
||||
module UrlRedirectFlow = TaintTracking::Global<UrlRedirectConfig>;
|
||||
|
||||
@@ -10,18 +10,23 @@ private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
import XpathInjectionCustomizations::XpathInjection
|
||||
|
||||
/** Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. */
|
||||
module XpathInjection {
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
import TaintTracking::Global<Config>
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
* DEPRECATED: Use `XpathInjectionFlow`
|
||||
*/
|
||||
deprecated module XpathInjection {
|
||||
import TaintTracking::Global<XpathInjectionConfig>
|
||||
}
|
||||
|
||||
private module XpathInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting "Xpath Injection" vulnerabilities.
|
||||
*/
|
||||
module XpathInjectionFlow = TaintTracking::Global<XpathInjectionConfig>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* missing full-anchored regular expressions.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `MissingFullAnchor::Configuration` is needed, otherwise
|
||||
* `MissingFullAnchorFlow` is needed, otherwise
|
||||
* `MissingFullAnchorCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
@@ -14,8 +14,9 @@ import MissingFullAnchorCustomizations::MissingFullAnchor
|
||||
/**
|
||||
* A taint tracking configuration for reasoning about
|
||||
* missing full-anchored regular expressions.
|
||||
* DEPRECATED: Use `MissingFullAnchorFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "MissingFullAnchor" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -24,3 +25,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module MissingFullAnchorConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for reasoning about missing full-anchored regular expressions.
|
||||
*/
|
||||
module MissingFullAnchorFlow = TaintTracking::Global<MissingFullAnchorConfig>;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Provides a taint tracking configuration for reasoning about polynomial
|
||||
* regular expression denial-of-service attacks.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is
|
||||
* needed. Otherwise, `PolynomialReDoSCustomizations` should be imported
|
||||
* instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `PolynomialReDoSFlow` is needed. Otherwise,
|
||||
* `PolynomialReDoSCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
@@ -13,15 +13,17 @@ private import codeql.ruby.TaintTracking
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting polynomial regular
|
||||
* expression denial of service vulnerabilities.
|
||||
* DEPRECATED: Use `PolynomialReDoSFlow`
|
||||
*/
|
||||
module PolynomialReDoS {
|
||||
deprecated module PolynomialReDoS {
|
||||
import PolynomialReDoSCustomizations::PolynomialReDoS
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting polynomial regular expression
|
||||
* denial of service vulnerabilities.
|
||||
* DEPRECATED: Use `PolynomialReDoSFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "PolynomialReDoS" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -35,3 +37,19 @@ module PolynomialReDoS {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module PolynomialReDoSConfig implements DataFlow::ConfigSig {
|
||||
private import PolynomialReDoSCustomizations::PolynomialReDoS
|
||||
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting polynomial regular
|
||||
* expression denial of service vulnerabilities.
|
||||
*/
|
||||
module PolynomialReDoSFlow = TaintTracking::Global<PolynomialReDoSConfig>;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting regexp injection vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `RegExpInjectionCustomizations` should be imported instead.
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `RegExpInjectionFlow` is needed, otherwise
|
||||
* `RegExpInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
@@ -12,8 +13,9 @@ import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting regexp injection vulnerabilities.
|
||||
* DEPRECATED: Use `RegExpInjectionFlow`
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "RegExpInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RegExpInjection::Source }
|
||||
@@ -26,3 +28,16 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof RegExpInjection::Sanitizer }
|
||||
}
|
||||
|
||||
private module RegExpInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RegExpInjection::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof RegExpInjection::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof RegExpInjection::Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-tracking for detecting regexp injection vulnerabilities.
|
||||
*/
|
||||
module RegExpInjectionFlow = TaintTracking::Global<RegExpInjectionConfig>;
|
||||
|
||||
@@ -6,7 +6,6 @@ private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplC
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||
private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.internal.AccessPathSyntax
|
||||
@@ -74,7 +73,7 @@ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
|
||||
*/
|
||||
predicate jumpStep = DataFlowPrivate::jumpStepTypeTracker/2;
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
|
||||
/** Holds if there is direct flow from `param` to a return. */
|
||||
pragma[nomagic]
|
||||
@@ -180,6 +179,7 @@ private predicate argumentPositionMatch(
|
||||
) {
|
||||
exists(DataFlowDispatch::ArgumentPosition apos |
|
||||
arg.sourceArgumentOf(call, apos) and
|
||||
not apos.isLambdaSelf() and
|
||||
DataFlowDispatch::parameterMatch(ppos, apos)
|
||||
)
|
||||
}
|
||||
@@ -333,13 +333,14 @@ predicate basicLoadStoreStep(
|
||||
predicate readStoreStepIntoSourceNode(
|
||||
Node nodeFrom, Node nodeTo, DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent
|
||||
) {
|
||||
exists(DataFlowPrivate::SynthSplatParameterElementNode mid |
|
||||
nodeFrom
|
||||
.(DataFlowPrivate::SynthSplatArgParameterNode)
|
||||
.isParameterOf(mid.getEnclosingCallable(), _) and
|
||||
loadContent = DataFlowPrivate::getPositionalContent(mid.getReadPosition()) and
|
||||
nodeTo = mid.getSplatParameterNode(_) and
|
||||
storeContent = DataFlowPrivate::getPositionalContent(mid.getStorePosition())
|
||||
exists(DataFlowPrivate::SynthSplatParameterShiftNode shift |
|
||||
shift.readFrom(nodeFrom, loadContent) and
|
||||
shift.storeInto(nodeTo, storeContent)
|
||||
)
|
||||
or
|
||||
exists(DataFlowPrivate::SynthSplatArgumentShiftNode shift |
|
||||
shift.readFrom(nodeFrom, loadContent) and
|
||||
shift.storeInto(nodeTo, storeContent)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Built-in Ruby queries now use the new DataFlow API.
|
||||
@@ -12,11 +12,10 @@
|
||||
* external/cwe/cwe-022
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.experimental.ZipSlipQuery
|
||||
import DataFlow::PathGraph
|
||||
import ZipSlipFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from ZipSlipFlow::PathNode source, ZipSlipFlow::PathNode sink
|
||||
where ZipSlipFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
|
||||
"potentially untrusted source"
|
||||
|
||||
@@ -12,12 +12,11 @@
|
||||
* external/cwe/cwe-180
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.experimental.UnicodeBypassValidationQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnicodeBypassValidationFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from UnicodeBypassValidationFlow::PathNode source, UnicodeBypassValidationFlow::PathNode sink
|
||||
where UnicodeBypassValidationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters.",
|
||||
sink.getNode(), "Unicode transformation (Unicode normalization)", source.getNode(),
|
||||
|
||||
45
ruby/ql/src/experimental/cwe-208/UnsafeHmacComparison.ql
Normal file
45
ruby/ql/src/experimental/cwe-208/UnsafeHmacComparison.ql
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @name Unsafe HMAC Comparison
|
||||
* @description An HMAC is being compared using the equality operator. This may be vulnerable to a cryptographic timing attack
|
||||
* because the equality operation does not occur in constant time."
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.0
|
||||
* @precision high
|
||||
* @id rb/unsafe-hmac-comparison
|
||||
* @tags security
|
||||
* external/cwe/cwe-208
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
private class OpenSslHmacSource extends DataFlow::Node {
|
||||
OpenSslHmacSource() {
|
||||
exists(API::Node hmacNode | hmacNode = API::getTopLevelMember("OpenSSL").getMember("HMAC") |
|
||||
this = hmacNode.getAMethodCall(["hexdigest", "to_s", "digest", "base64digest"])
|
||||
or
|
||||
this = hmacNode.getAnInstantiation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module UnsafeHmacComparison {
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof OpenSslHmacSource }
|
||||
|
||||
// Holds if a given sink is an Equality Operation (== or !=)
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
any(EqualityOperation eqOp).getAnOperand() = sink.asExpr().getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
import DataFlow::Global<Config>
|
||||
}
|
||||
|
||||
private import UnsafeHmacComparison::PathGraph
|
||||
|
||||
from UnsafeHmacComparison::PathNode source, UnsafeHmacComparison::PathNode sink
|
||||
where UnsafeHmacComparison::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This comparison is potentially vulnerable to a timing attack."
|
||||
21
ruby/ql/src/experimental/cwe-208/UnsafeHmacComparison.qlhelp
Normal file
21
ruby/ql/src/experimental/cwe-208/UnsafeHmacComparison.qlhelp
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Using the `==` or `!=` operator to compare a known valid HMAC with a user-supplied HMAC digest could lead to a timing attack, as these operations do not occur in constant time.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Instead of using `==` or `!=` to compare a known valid HMAC with a user-supplied HMAC digest use Rack::Utils#secure_compare, ActiveSupport::SecurityUtils#secure_compare or OpenSSL.secure_compare
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In this example, the HMAC is validated using the `==` operation.
|
||||
</p>
|
||||
<sample src="./examples/unsafe_hmac_comparison.rb" />
|
||||
</example>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,11 @@
|
||||
class UnsafeHmacComparison
|
||||
def verify_hmac(host, hmac, salt)
|
||||
sha1 = OpenSSL::Digest.new('sha1')
|
||||
if OpenSSL::HMAC.digest(sha1, salt, host) == hmac
|
||||
puts "HMAC verified"
|
||||
else
|
||||
puts "HMAC not verified"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ import codeql.ruby.DataFlow
|
||||
import codeql.ruby.dataflow.internal.DataFlowPublic
|
||||
import codeql.ruby.security.ConditionalBypassQuery
|
||||
import codeql.ruby.security.SensitiveActions
|
||||
import DataFlow::PathGraph
|
||||
import ConditionalBypassFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Holds if the value of `nd` flows into `guard`.
|
||||
@@ -69,15 +69,18 @@ class SensitiveActionGuardComparisonOperand extends Sink {
|
||||
* control if `action` should be executed or not.
|
||||
*/
|
||||
predicate isTaintedGuardForSensitiveAction(
|
||||
DataFlow::PathNode sink, DataFlow::PathNode source, SensitiveAction action
|
||||
ConditionalBypassFlow::PathNode sink, ConditionalBypassFlow::PathNode source,
|
||||
SensitiveAction action
|
||||
) {
|
||||
action = sink.getNode().(Sink).getAction() and
|
||||
// exclude the intermediary sink
|
||||
not sink.getNode() instanceof SensitiveActionGuardComparisonOperand and
|
||||
exists(Configuration cfg | cfg.hasFlowPath(source, sink))
|
||||
ConditionalBypassFlow::flowPath(source, sink)
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveAction action
|
||||
from
|
||||
ConditionalBypassFlow::PathNode source, ConditionalBypassFlow::PathNode sink,
|
||||
SensitiveAction action
|
||||
where isTaintedGuardForSensitiveAction(sink, source, action)
|
||||
select sink.getNode(), source, sink, "This condition guards a sensitive $@, but a $@ controls it.",
|
||||
action, "action", source.getNode(), "user-provided value"
|
||||
|
||||
@@ -16,7 +16,6 @@ import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class DecompressionApiUse extends DataFlow::Node {
|
||||
private DataFlow::CallNode call;
|
||||
@@ -34,18 +33,20 @@ class DecompressionApiUse extends DataFlow::Node {
|
||||
DataFlow::CallNode getCall() { result = call }
|
||||
}
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "DecompressionApiUse" }
|
||||
|
||||
private module DecompressionApiConfig implements DataFlow::ConfigSig {
|
||||
// this predicate will be used to constrain our query to find instances where only remote user-controlled data flows to the sink
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
// our Decompression APIs defined above will be the sinks we use for this query
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionApiUse }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionApiUse }
|
||||
}
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
private module DecompressionApiFlow = TaintTracking::Global<DecompressionApiConfig>;
|
||||
|
||||
import DecompressionApiFlow::PathGraph
|
||||
|
||||
from DecompressionApiFlow::PathNode source, DecompressionApiFlow::PathNode sink
|
||||
where DecompressionApiFlow::flowPath(source, sink)
|
||||
select sink.getNode().(DecompressionApiUse), source, sink,
|
||||
"This call to $@ is unsafe because user-controlled data is used to set the object being decompressed, which could lead to a denial of service attack or malicious code extracted from an unknown source.",
|
||||
sink.getNode().(DecompressionApiUse).getCall(),
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.ImproperLdapAuthQuery
|
||||
import codeql.ruby.Concepts
|
||||
import DataFlow::PathGraph
|
||||
import ImproperLdapAuthFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from ImproperLdapAuthFlow::PathNode source, ImproperLdapAuthFlow::PathNode sink
|
||||
where ImproperLdapAuthFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This LDAP authencation depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.LdapInjectionQuery
|
||||
import LdapInjection::PathGraph
|
||||
import LdapInjectionFlow::PathGraph
|
||||
|
||||
from LdapInjection::PathNode source, LdapInjection::PathNode sink
|
||||
where LdapInjection::flowPath(source, sink)
|
||||
from LdapInjectionFlow::PathNode source, LdapInjectionFlow::PathNode sink
|
||||
where LdapInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This LDAP query depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,7 +15,6 @@ import codeql.ruby.DataFlow
|
||||
import codeql.ruby.controlflow.CfgNodes
|
||||
import codeql.ruby.frameworks.ActionController
|
||||
import codeql.ruby.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
// any `request` calls in an action method
|
||||
class Request extends DataFlow::CallNode {
|
||||
@@ -73,10 +72,8 @@ class RequestGet extends DataFlow::CallNode {
|
||||
}
|
||||
}
|
||||
|
||||
class HttpVerbConfig extends TaintTracking::Configuration {
|
||||
HttpVerbConfig() { this = "HttpVerbConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
private module HttpVerbConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RequestMethod or
|
||||
source instanceof RequestRequestMethod or
|
||||
source instanceof RequestEnvMethod or
|
||||
@@ -85,13 +82,17 @@ class HttpVerbConfig extends TaintTracking::Configuration {
|
||||
source instanceof RequestGet
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(ExprNodes::ConditionalExprCfgNode c | c.getCondition() = sink.asExpr()) or
|
||||
exists(ExprNodes::CaseExprCfgNode c | c.getValue() = sink.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
private module HttpVerbFlow = TaintTracking::Global<HttpVerbConfig>;
|
||||
|
||||
import HttpVerbFlow::PathGraph
|
||||
|
||||
from HttpVerbFlow::PathNode source, HttpVerbFlow::PathNode sink
|
||||
where HttpVerbFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mapping resources and verbs to specific methods."
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.TemplateInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import TemplateInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from TemplateInjectionFlow::PathNode source, TemplateInjectionFlow::PathNode sink
|
||||
where TemplateInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This template depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,7 +15,6 @@ import codeql.ruby.Concepts
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.frameworks.ActionController
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Gets a call to `request` in an ActionController controller class.
|
||||
@@ -42,16 +41,18 @@ class WeakParams extends DataFlow::CallNode {
|
||||
* A Taint tracking config where the source is a weak params access in a controller and the sink
|
||||
* is a method call of a model class
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "WeakParamsConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof WeakParams }
|
||||
private module WeakParamsConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node instanceof WeakParams }
|
||||
|
||||
// the sink is an instance of a Model class that receives a method call
|
||||
override predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() }
|
||||
predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() }
|
||||
}
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
private module WeakParamsFlow = TaintTracking::Global<WeakParamsConfig>;
|
||||
|
||||
import WeakParamsFlow::PathGraph
|
||||
|
||||
from WeakParamsFlow::PathNode source, WeakParamsFlow::PathNode sink
|
||||
where WeakParamsFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"By exposing all keys in request parameters or by blindy accessing them, unintended parameters could be used and lead to mass-assignment or have other unexpected side-effects. It is safer to follow the 'strong parameters' pattern in Rails, which is outlined here: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.XpathInjectionQuery
|
||||
import XpathInjection::PathGraph
|
||||
import XpathInjectionFlow::PathGraph
|
||||
|
||||
from XpathInjection::PathNode source, XpathInjection::PathNode sink
|
||||
where XpathInjection::flowPath(source, sink)
|
||||
from XpathInjectionFlow::PathNode source, XpathInjectionFlow::PathNode sink
|
||||
where XpathInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -12,17 +12,17 @@ import internal.TaintMetrics
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
|
||||
class BasicTaintConfiguration extends TaintTracking::Configuration {
|
||||
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
|
||||
private module BasicTaintConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node = relevantTaintSource(_) }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource(_) }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// To reduce noise from synthetic nodes, only count nodes that have an associated expression.
|
||||
exists(node.asExpr().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
private module BasicTaintFlow = TaintTracking::Global<BasicTaintConfig>;
|
||||
|
||||
from DataFlow::Node node
|
||||
where any(BasicTaintConfiguration cfg).hasFlow(_, node)
|
||||
where BasicTaintFlow::flow(_, node)
|
||||
select node, "Tainted node"
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.regexp.MissingFullAnchorQuery
|
||||
import DataFlow::PathGraph
|
||||
import MissingFullAnchorFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where config.hasFlowPath(source, sink) and sink.getNode() = sinkNode
|
||||
from MissingFullAnchorFlow::PathNode source, MissingFullAnchorFlow::PathNode sink, Sink sinkNode
|
||||
where MissingFullAnchorFlow::flowPath(source, sink) and sink.getNode() = sinkNode
|
||||
select sink, source, sink, "This value depends on $@, and is $@ against a $@.", source.getNode(),
|
||||
source.getNode().(Source).describe(), sinkNode.getCallNode(), "checked", sinkNode.getRegex(),
|
||||
"badly anchored regular expression"
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.security.PathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import PathInjectionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathInjectionFlow::PathNode source, PathInjectionFlow::PathNode sink
|
||||
where PathInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.security.CommandInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import CommandInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode
|
||||
from CommandInjectionFlow::PathNode source, CommandInjectionFlow::PathNode sink, Source sourceNode
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
CommandInjectionFlow::flowPath(source, sink) and
|
||||
sourceNode = source.getNode()
|
||||
select sink.getNode(), source, sink, "This command depends on a $@.", sourceNode,
|
||||
sourceNode.getSourceType()
|
||||
|
||||
@@ -16,33 +16,14 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
import DataFlow::PathGraph
|
||||
import codeql.ruby.security.KernelOpenQuery
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "KernelOpen" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(AmbiguousPathCall r).getPathArgument()
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
import KernelOpenFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
DataFlow::Node sourceNode, DataFlow::CallNode call
|
||||
KernelOpenFlow::PathNode source, KernelOpenFlow::PathNode sink, DataFlow::Node sourceNode,
|
||||
DataFlow::CallNode call
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
KernelOpenFlow::flowPath(source, sink) and
|
||||
sourceNode = source.getNode() and
|
||||
call.getArgument(0) = sink.getNode()
|
||||
select sink.getNode(), source, sink,
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.UnsafeShellCommandConstructionQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeShellCommandConstructionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
from
|
||||
UnsafeShellCommandConstructionFlow::PathNode source,
|
||||
UnsafeShellCommandConstructionFlow::PathNode sink, Sink sinkNode
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
UnsafeShellCommandConstructionFlow::flowPath(source, sink) and
|
||||
sinkNode = sink.getNode()
|
||||
select sinkNode.getStringConstruction(), source, sink,
|
||||
"This " + sinkNode.describe() + " which depends on $@ is later used in a $@.", source.getNode(),
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.security.ReflectedXSSQuery
|
||||
import DataFlow::PathGraph
|
||||
import ReflectedXssFlow::PathGraph
|
||||
|
||||
from ReflectedXss::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from ReflectedXssFlow::PathNode source, ReflectedXssFlow::PathNode sink
|
||||
where ReflectedXssFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.security.StoredXSSQuery
|
||||
import StoredXss::PathGraph
|
||||
import StoredXssFlow::PathGraph
|
||||
|
||||
from StoredXss::PathNode source, StoredXss::PathNode sink
|
||||
where StoredXss::flowPath(source, sink)
|
||||
from StoredXssFlow::PathNode source, StoredXssFlow::PathNode sink
|
||||
where StoredXssFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "stored value"
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.UnsafeHtmlConstructionQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeHtmlConstructionFlow::PathGraph
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where cfg.hasFlowPath(source, sink) and sink.getNode() = sinkNode
|
||||
from
|
||||
UnsafeHtmlConstructionFlow::PathNode source, UnsafeHtmlConstructionFlow::PathNode sink,
|
||||
Sink sinkNode
|
||||
where UnsafeHtmlConstructionFlow::flowPath(source, sink) and sink.getNode() = sinkNode
|
||||
select sinkNode, source, sink,
|
||||
"This " + sinkNode.getSinkType() + " which depends on $@ might later allow $@.", source.getNode(),
|
||||
"library input", sinkNode.getXssSink(), "cross-site scripting"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.SqlInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import SqlInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from SqlInjectionFlow::PathNode source, SqlInjectionFlow::PathNode sink
|
||||
where SqlInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,21 +14,22 @@
|
||||
* external/cwe/cwe-116
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.security.CodeInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.security.CodeInjectionQuery
|
||||
import CodeInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode
|
||||
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink, Source sourceNode
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
CodeInjectionFlow::flowPath(source, sink) and
|
||||
sourceNode = source.getNode() and
|
||||
// removing duplications of the same path, but different flow-labels.
|
||||
sink =
|
||||
min(DataFlow::PathNode otherSink |
|
||||
config.hasFlowPath(any(DataFlow::PathNode s | s.getNode() = sourceNode), otherSink) and
|
||||
min(CodeInjectionFlow::PathNode otherSink |
|
||||
CodeInjectionFlow::flowPath(any(CodeInjectionFlow::PathNode s | s.getNode() = sourceNode),
|
||||
otherSink) and
|
||||
otherSink.getNode() = sink.getNode()
|
||||
|
|
||||
otherSink order by otherSink.getState()
|
||||
otherSink order by otherSink.getState().getStringRepresentation()
|
||||
)
|
||||
select sink.getNode(), source, sink, "This code execution depends on a $@.", sourceNode,
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.UnsafeCodeConstructionQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeCodeConstructionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where cfg.hasFlowPath(source, sink) and sinkNode = sink.getNode()
|
||||
from
|
||||
UnsafeCodeConstructionFlow::PathNode source, UnsafeCodeConstructionFlow::PathNode sink,
|
||||
Sink sinkNode
|
||||
where UnsafeCodeConstructionFlow::flowPath(source, sink) and sinkNode = sink.getNode()
|
||||
select sink.getNode(), source, sink,
|
||||
"This " + sinkNode.getSinkType() + " which depends on $@ is later $@.", source.getNode(),
|
||||
"library input", sinkNode.getCodeSink(), "interpreted as code"
|
||||
|
||||
@@ -1,8 +1,139 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<include src="IncompleteSanitization.qhelp" />
|
||||
<overview>
|
||||
<p>
|
||||
Sanitizing untrusted input is a common technique for preventing injection attacks and other security
|
||||
vulnerabilities. Regular expressions are often used to perform this sanitization. However, when the
|
||||
regular expression matches multiple consecutive characters, replacing it just once
|
||||
can result in the unsafe text re-appearing in the sanitized input.
|
||||
</p>
|
||||
<p>
|
||||
Attackers can exploit this issue by crafting inputs that, when sanitized with an ineffective regular
|
||||
expression, still contain malicious code or content. This can lead to code execution, data exposure,
|
||||
or other vulnerabilities.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
To prevent this issue, it is highly recommended to use a well-tested sanitization library whenever
|
||||
possible. These libraries are more likely to handle corner cases and ensure effective sanitization.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If a library is not an option, you can consider alternative strategies to fix the issue. For example,
|
||||
applying the regular expression replacement repeatedly until no more replacements can be performed, or rewriting the regular
|
||||
expression to match single characters instead of the entire unsafe text.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
Consider the following Ruby code that aims to remove all HTML comment start and end tags:
|
||||
</p>
|
||||
|
||||
<sample language="ruby">
|
||||
str.gsub(/<!--|--!?>/, "")
|
||||
</sample>
|
||||
|
||||
<p>
|
||||
Given the input string "<!<!--- comment --->>", the output will be "<!-- comment -->",
|
||||
which still contains an HTML comment.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
One possible fix for this issue is to apply the regular expression replacement repeatedly until no
|
||||
more replacements can be performed. This ensures that the unsafe text does not re-appear in the sanitized input, effectively
|
||||
removing all instances of the targeted pattern:
|
||||
</p>
|
||||
|
||||
<sample language="ruby">
|
||||
def remove_html_comments(input)
|
||||
previous = nil
|
||||
while input != previous
|
||||
previous = input
|
||||
input = input.gsub(/<!--|--!?>/, "")
|
||||
end
|
||||
input
|
||||
end
|
||||
</sample>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
Another example is the following regular expression intended to remove script tags:
|
||||
</p>
|
||||
|
||||
<sample language="ruby">
|
||||
str.gsub(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/, "")
|
||||
</sample>
|
||||
|
||||
<p>
|
||||
If the input string is "<scrip<script>is removed</script>t>alert(123)</script>",
|
||||
the output will be "<script>alert(123)</script>", which still contains a script tag.
|
||||
</p>
|
||||
<p>
|
||||
A fix for this issue is to rewrite the regular expression to match single characters
|
||||
("<" and ">") instead of the entire unsafe text. This simplifies the sanitization process
|
||||
and ensures that all potentially unsafe characters are removed:
|
||||
</p>
|
||||
<sample language="ruby">
|
||||
def remove_all_html_tags(input)
|
||||
input.gsub(/<|>/, "")
|
||||
end
|
||||
</sample>
|
||||
|
||||
<p>
|
||||
Another potential fix is to use the popular <code>sanitize</code> gem.
|
||||
It keeps most of the safe HTML tags while removing all unsafe tags and attributes.
|
||||
</p>
|
||||
<sample language="javascript">
|
||||
require 'sanitize'
|
||||
|
||||
def sanitize_html(input)
|
||||
Sanitize.fragment(input)
|
||||
end
|
||||
</sample>
|
||||
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
Lastly, consider a path sanitizer using the regular expression <code>/\.\.\//</code>:
|
||||
</p>
|
||||
|
||||
<sample language="ruby">
|
||||
str.gsub(/\.\.\//, "")
|
||||
</sample>
|
||||
|
||||
<p>
|
||||
The regular expression attempts to strip out all occurences of <code>/../</code> from <code>str</code>.
|
||||
This will not work as expected: for the string <code>/./.././</code>, for example, it will remove the single
|
||||
occurrence of <code>/../</code> in the middle, but the remainder of the string then becomes
|
||||
<code>/../</code>, which is another instance of the substring we were trying to remove.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A possible fix for this issue is to use the <code>File.sanitize</code> function from the Ruby Facets gem.
|
||||
This library is specifically designed to handle path sanitization, and should handle all corner cases
|
||||
and ensure effective sanitization:
|
||||
</p>
|
||||
|
||||
<sample language="ruby">
|
||||
require 'facets'
|
||||
|
||||
def sanitize_path(input)
|
||||
File.sanitize(input)
|
||||
end
|
||||
</sample>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP Top 10: <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">A1 Injection</a>.</li>
|
||||
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/6659351/removing-all-script-tags-from-html-with-js-regular-expression">Removing all script tags from HTML with JS regular expression</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -37,18 +37,6 @@ An even safer alternative is to design the application so that sanitization is n
|
||||
Otherwise, make sure to use <code>String#gsub</code> rather than <code>String#sub</code>, to ensure
|
||||
that all occurrences are replaced, and remember to escape backslashes if applicable.
|
||||
</p>
|
||||
<p>
|
||||
Note, however, that this is generally <i>not</i> sufficient for replacing multi-character strings:
|
||||
the <code>String#gsub</code> method performs only one pass over the input string, and will not
|
||||
replace further instances of the string that result from earlier replacements.
|
||||
</p>
|
||||
<p>
|
||||
For example, consider the code snippet <code>s.gsub /\/\.\.\//, ""</code>, which attempts to strip
|
||||
out all occurrences of <code>/../</code> from <code>s</code>. This will not work as expected: for the
|
||||
string <code>/./.././</code>, for example, it will remove the single occurrence of <code>/../</code>
|
||||
in the middle, but the remainder of the string then becomes <code>/../</code>, which is another
|
||||
instance of the substring we were trying to remove.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
import DataFlow::PathGraph
|
||||
import codeql.ruby.security.LogInjectionQuery
|
||||
import LogInjectionFlow::PathGraph
|
||||
|
||||
from LogInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from LogInjectionFlow::PathNode source, LogInjectionFlow::PathNode sink
|
||||
where LogInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Log entry depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import DataFlow::PathGraph
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.regexp.PolynomialReDoSCustomizations::PolynomialReDoS as PR
|
||||
import codeql.ruby.security.regexp.PolynomialReDoSQuery
|
||||
import PolynomialReDoSFlow::PathGraph
|
||||
|
||||
from
|
||||
PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
PolynomialReDoS::Sink sinkNode, PolynomialReDoS::PolynomialBackTrackingTerm regexp
|
||||
PolynomialReDoSFlow::PathNode source, PolynomialReDoSFlow::PathNode sink, PR::Sink sinkNode,
|
||||
PR::PolynomialBackTrackingTerm regexp
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
PolynomialReDoSFlow::flowPath(source, sink) and
|
||||
sinkNode = sink.getNode() and
|
||||
regexp = sinkNode.getRegExp()
|
||||
select sinkNode.getHighlight(), source, sink,
|
||||
"This $@ that depends on a $@ may run slow on strings " + regexp.getPrefixMessage() +
|
||||
"with many repetitions of '" + regexp.getPumpString() + "'.", regexp, "regular expression",
|
||||
source.getNode(), source.getNode().(PolynomialReDoS::Source).describe()
|
||||
source.getNode(), source.getNode().(PR::Source).describe()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user