Merge branch 'main' into henrymercer/rc-3.11-mergeback

This commit is contained in:
Henry Mercer
2023-10-03 16:30:23 +01:00
1450 changed files with 135236 additions and 95589 deletions

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Improved support for flow through captured variables that properly adheres to inter-procedural control flow.

View File

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

View 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()

View File

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

View File

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

View File

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

View File

@@ -115,6 +115,8 @@ private module ConditionalCompletionSplitting {
}
}
class ConditionalCompletionSplit = ConditionalCompletionSplitting::ConditionalCompletionSplit;
module EnsureSplitting {
/**
* The type of a split `ensure` node.

View File

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

View File

@@ -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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() }
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, _, _))
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Built-in Ruby queries now use the new DataFlow API.

View File

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

View File

@@ -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(),

View 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."

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

View File

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

View File

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

View File

@@ -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(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(/&lt;!--|--!?&gt;/, "")
</sample>
<p>
Given the input string "&lt;!&lt;!--- comment ---&gt;&gt;", the output will be "&lt;!-- comment --&gt;",
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(/&lt;!--|--!?&gt;/, "")
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(/&lt;script\b[^&lt;]*(?:(?!&lt;\/script&gt;)&lt;[^&lt;]*)*&lt;\/script&gt;/, "")
</sample>
<p>
If the input string is "&lt;scrip&lt;script&gt;is removed&lt;/script&gt;t&gt;alert(123)&lt;/script&gt;",
the output will be "&lt;script&gt;alert(123)&lt;/script&gt;", which still contains a script tag.
</p>
<p>
A fix for this issue is to rewrite the regular expression to match single characters
("&lt;" and "&gt;") 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(/&lt;|&gt;/, "")
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>

View File

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

View File

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

View File

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