Merge remote-tracking branch 'upstream/main' into incomplete-hostname

This commit is contained in:
Arthur Baars
2022-03-16 12:31:12 +01:00
1166 changed files with 65711 additions and 51908 deletions

View File

@@ -1,3 +1,10 @@
## 0.0.11
### Minor Analysis Improvements
* The `Regex` class is now an abstract class that extends `StringlikeLiteral` with implementations for `RegExpLiteral` and string literals that 'flow' into functions that are known to interpret string arguments as regular expressions such as `Regex.new` and `String.match`.
* The regular expression parser now groups sequences of normal characters. This reduces the number of instances of `RegExpNormalChar`.
## 0.0.10
### Minor Analysis Improvements

View File

@@ -0,0 +1,5 @@
---
category: deprecated
---
* Many classes/predicates/modules that had upper-case acronyms have been renamed to follow our style-guide.
The old name still exists as a deprecated alias.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The regular expression parser now groups sequences of normal characters. This reduces the number of instances of `RegExpNormalChar`.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added `OrmWriteAccess` concept to model data written to a database using an object-relational mapping (ORM) library.

View File

@@ -0,0 +1,4 @@
---
category: breaking
---
* The flow state variants of `isBarrier` and `isAdditionalFlowStep` are no longer exposed in the taint tracking library. The `isSanitizer` and `isAdditionalTaintStep` predicates should be used instead.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* `getConstantValue()` now returns the contents of strings and symbols after escape sequences have been interpreted. For example, for the Ruby string literal `"\n"`, `getConstantValue().getString()` previously returned a QL string with two characters, a backslash followed by `n`; now it returns the single-character string "\n" (U+000A, known as newline).
* `getConstantValue().getInt()` previously returned incorrect values for integers larger than 2<sup>31</sup>-1 (the largest value that can be represented by the QL `int` type). It now returns no result in those cases.

View File

@@ -1,4 +1,6 @@
---
category: minorAnalysis
---
## 0.0.11
### Minor Analysis Improvements
* The `Regex` class is now an abstract class that extends `StringlikeLiteral` with implementations for `RegExpLiteral` and string literals that 'flow' into functions that are known to interpret string arguments as regular expressions such as `Regex.new` and `String.match`.
* The regular expression parser now groups sequences of normal characters. This reduces the number of instances of `RegExpNormalChar`.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.0.10
lastReleaseVersion: 0.0.11

View File

@@ -0,0 +1,129 @@
/**
* Provides predicates for working with numeric values and their string
* representations.
*/
/**
* Gets the integer value of `binary` when interpreted as binary. `binary` must
* contain only the digits 0 and 1. For values greater than
* 01111111111111111111111111111111 (2^31-1, the maximum value that `int` can
* represent), there is no result.
*
* ```
* "0" => 0
* "01" => 1
* "1010101" => 85
* ```
*/
bindingset[binary]
int parseBinaryInt(string binary) {
exists(string stripped | stripped = stripLeadingZeros(binary) |
stripped.length() <= 31 and
result >= 0 and
result =
sum(int index, string c, int digit |
c = stripped.charAt(index) and
digit = "01".indexOf(c)
|
twoToThe(stripped.length() - 1 - index) * digit
)
)
}
/**
* Gets the integer value of `hex` when interpreted as hex. `hex` must be a
* valid hexadecimal string. For values greater than 7FFFFFFF (2^31-1, the
* maximum value that `int` can represent), there is no result.
*
* ```
* "0" => 0
* "FF" => 255
* "f00d" => 61453
* ```
*/
bindingset[hex]
int parseHexInt(string hex) {
exists(string stripped | stripped = stripLeadingZeros(hex) |
stripped.length() <= 8 and
result >= 0 and
result =
sum(int index, string c |
c = stripped.charAt(index)
|
sixteenToThe(stripped.length() - 1 - index) * toHex(c)
)
)
}
/**
* Gets the integer value of `octal` when interpreted as octal. `octal` must be
* a valid octal string containing only the digits 0-7. For values greater than
* 17777777777 (2^31-1, the maximum value that `int` can represent), there is no
* result.
*
* ```
* "0" => 0
* "77" => 63
* "76543210" => 16434824
* ```
*/
bindingset[octal]
int parseOctalInt(string octal) {
exists(string stripped | stripped = stripLeadingZeros(octal) |
stripped.length() <= 11 and
result >= 0 and
result =
sum(int index, string c, int digit |
c = stripped.charAt(index) and
digit = "01234567".indexOf(c)
|
eightToThe(stripped.length() - 1 - index) * digit
)
)
}
/** Gets the integer value of the `hex` char. */
private int toHex(string hex) {
hex = [0 .. 9].toString() and
result = hex.toInt()
or
result = 10 and hex = ["a", "A"]
or
result = 11 and hex = ["b", "B"]
or
result = 12 and hex = ["c", "C"]
or
result = 13 and hex = ["d", "D"]
or
result = 14 and hex = ["e", "E"]
or
result = 15 and hex = ["f", "F"]
}
/**
* Gets the value of 16 to the power of `n`. Holds only for `n` in the range
* 0..7 (inclusive).
*/
int sixteenToThe(int n) {
// 16**7 is the largest power of 16 that fits in an int.
n in [0 .. 7] and result = 1.bitShiftLeft(4 * n)
}
/**
* Gets the value of 8 to the power of `n`. Holds only for `n` in the range
* 0..10 (inclusive).
*/
int eightToThe(int n) {
// 8**10 is the largest power of 8 that fits in an int.
n in [0 .. 10] and result = 1.bitShiftLeft(3 * n)
}
/**
* Gets the value of 2 to the power of `n`. Holds only for `n` in the range
* 0..30 (inclusive).
*/
int twoToThe(int n) { n in [0 .. 30] and result = 1.bitShiftLeft(n) }
/** Gets `s` with any leading "0" characters removed. */
bindingset[s]
private string stripLeadingZeros(string s) { result = s.regexpCapture("0*(.*)", 1) }

View File

@@ -625,6 +625,35 @@ module OrmInstantiation {
}
}
/**
* A data flow node that writes persistent data.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `PersistentWriteAccess::Range` instead.
*/
class PersistentWriteAccess extends DataFlow::Node instanceof PersistentWriteAccess::Range {
/**
* Gets the data flow node corresponding to the written value.
*/
DataFlow::Node getValue() { result = super.getValue() }
}
/** Provides a class for modeling new persistent write access APIs. */
module PersistentWriteAccess {
/**
* A data flow node that writes persistent data.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `PersistentWriteAccess` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the data flow node corresponding to the written value.
*/
abstract DataFlow::Node getValue();
}
}
/**
* A data-flow node that may set or unset Cross-site request forgery protection.
*

View File

@@ -24,21 +24,8 @@ class Expr extends Stmt, TExpr {
}
}
/**
* A reference to the current object. For example:
* - `self == other`
* - `self.method_name`
* - `def self.method_name ... end`
*
* This also includes implicit references to the current object in method
* calls. For example, the method call `foo(123)` has an implicit `self`
* receiver, and is equivalent to the explicit `self.foo(123)`.
*/
class Self extends Expr, TSelf {
final override string getAPrimaryQlClass() { result = "Self" }
final override string toString() { result = "self" }
}
/** DEPRECATED: Use `SelfVariableAccess` instead. */
deprecated class Self = SelfVariableAccess;
/**
* A sequence of expressions in the right-hand side of an assignment or

View File

@@ -230,13 +230,18 @@ class StringTextComponent extends StringComponent, TStringTextComponentNonRegexp
StringTextComponent() { this = TStringTextComponentNonRegexp(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = this.getRawText() }
final override ConstantValue::ConstantStringValue getConstantValue() {
result.isString(g.getValue())
result.isString(this.getUnescapedText())
}
final override string getAPrimaryQlClass() { result = "StringTextComponent" }
/** Gets the text of this component as it appears in the source code. */
final string getRawText() { result = g.getValue() }
final private string getUnescapedText() { result = unescapeTextComponent(this.getRawText()) }
}
/**
@@ -247,13 +252,18 @@ class StringEscapeSequenceComponent extends StringComponent, TStringEscapeSequen
StringEscapeSequenceComponent() { this = TStringEscapeSequenceComponentNonRegexp(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = this.getRawText() }
final override ConstantValue::ConstantStringValue getConstantValue() {
result.isString(g.getValue())
result.isString(this.getUnescapedText())
}
final override string getAPrimaryQlClass() { result = "StringEscapeSequenceComponent" }
/** Gets the text of this component as it appears in the source code. */
final string getRawText() { result = g.getValue() }
final private string getUnescapedText() { result = unescapeEscapeSequence(this.getRawText()) }
}
/**

View File

@@ -201,7 +201,16 @@ class ClassVariableWriteAccess extends ClassVariableAccess, VariableWriteAccess
/** An access to a class variable where the value is read. */
class ClassVariableReadAccess extends ClassVariableAccess, VariableReadAccess { }
/** An access to the `self` variable */
/**
* An access to the `self` variable. For example:
* - `self == other`
* - `self.method_name`
* - `def self.method_name ... end`
*
* This also includes implicit references to the current object in method
* calls. For example, the method call `foo(123)` has an implicit `self`
* receiver, and is equivalent to the explicit `self.foo(123)`.
*/
class SelfVariableAccess extends LocalVariableAccess instanceof SelfVariableAccessImpl {
final override string getAPrimaryQlClass() { result = "SelfVariableAccess" }
}

View File

@@ -3,43 +3,23 @@ private import AST
private import Constant
private import TreeSitter
private import codeql.ruby.controlflow.CfgNodes
private import codeql.NumberUtils
int parseInteger(Ruby::Integer i) {
exists(string s | s = i.getValue().toLowerCase().replaceAll("_", "") |
s.charAt(0) != "0" and
result = s.toInt()
or
exists(string str, string values, int shift |
s.matches("0b%") and
values = "01" and
str = s.suffix(2) and
shift = 1
or
s.matches("0x%") and
values = "0123456789abcdef" and
str = s.suffix(2) and
shift = 4
or
s.charAt(0) = "0" and
not s.charAt(1) = ["b", "x", "o"] and
values = "01234567" and
str = s.suffix(1) and
shift = 3
or
s.matches("0o%") and
values = "01234567" and
str = s.suffix(2) and
shift = 3
|
result =
sum(int index, string c, int v, int exp |
c = str.charAt(index) and
v = values.indexOf(c.toLowerCase()) and
exp = str.length() - index - 1
|
v.bitShiftLeft((str.length() - index - 1) * shift)
)
)
s.matches("0b%") and result = parseBinaryInt(s.suffix(2))
or
s.matches("0x%") and result = parseHexInt(s.suffix(2))
or
s.charAt(0) = "0" and
not s.charAt(1) = ["b", "x", "o"] and
result = parseOctalInt(s.suffix(1))
or
s.matches("0o%") and
result = parseOctalInt(s.suffix(2))
)
}
@@ -148,16 +128,85 @@ private class RequiredFileLiteralConstantValue extends RequiredConstantValue {
private class RequiredStringTextComponentConstantValue extends RequiredConstantValue {
override predicate requiredString(string s) {
s = any(Ruby::Token t | exists(TStringTextComponentNonRegexp(t))).getValue()
s =
unescapeTextComponent(any(Ruby::Token t | exists(TStringTextComponentNonRegexp(t))).getValue())
}
}
private class RequiredStringEscapeSequenceComponentConstantValue extends RequiredConstantValue {
override predicate requiredString(string s) {
s = any(Ruby::Token t | exists(TStringEscapeSequenceComponentNonRegexp(t))).getValue()
s =
unescapeEscapeSequence(any(Ruby::Token t | exists(TStringEscapeSequenceComponentNonRegexp(t)))
.getValue())
}
}
/**
* Gets the string represented by the escape sequence in `escaped`. For example:
*
* ```
* \\ => \
* \141 => a
* \u0078 => x
* ```
*/
bindingset[escaped]
string unescapeEscapeSequence(string escaped) {
result = unescapeKnownEscapeSequence(escaped)
or
// Any other character following a backslash is just that character.
not exists(unescapeKnownEscapeSequence(escaped)) and
result = escaped.suffix(1)
}
bindingset[escaped]
private string unescapeKnownEscapeSequence(string escaped) {
escaped = "\\\\" and result = "\\"
or
escaped = "\\'" and result = "'"
or
escaped = "\\\"" and result = "\""
or
escaped = "\\a" and result = 7.toUnicode()
or
escaped = "\\b" and result = 8.toUnicode()
or
escaped = "\\t" and result = "\t"
or
escaped = "\\n" and result = "\n"
or
escaped = "\\v" and result = 11.toUnicode()
or
escaped = "\\f" and result = 12.toUnicode()
or
escaped = "\\r" and result = "\r"
or
escaped = "\\e" and result = 27.toUnicode()
or
escaped = "\\s" and result = " "
or
escaped = ["\\c?", "\\C-?"] and result = 127.toUnicode()
or
result = parseOctalInt(escaped.regexpCapture("\\\\([0-7]{1,3})", 1)).toUnicode()
or
result = parseHexInt(escaped.regexpCapture("\\\\x([0-9a-fA-F]{1,2})", 1)).toUnicode()
or
result = parseHexInt(escaped.regexpCapture("\\\\u([0-9a-fA-F]{4})", 1)).toUnicode()
or
result = parseHexInt(escaped.regexpCapture("\\\\u\\{([0-9a-fA-F]{1,6})\\}", 1)).toUnicode()
}
/**
* Gets the result of unescaping a string text component by replacing `\\` and
* `\'` with `\` and `'`, respectively.
*
* ```rb
* 'foo\\bar \'baz\'' # foo\bar 'baz'
* ```
*/
bindingset[text]
string unescapeTextComponent(string text) { result = text.regexpReplaceAll("\\\\(['\\\\])", "$1") }
class TRegExpComponent =
TStringTextComponentRegexp or TStringEscapeSequenceComponentRegexp or
TStringInterpolationComponentRegexp;

View File

@@ -73,7 +73,7 @@ private module Cached {
m = resolveConstantReadAccess(c.getReceiver())
or
m = enclosingModule(c).getModule() and
c.getReceiver() instanceof Self
c.getReceiver() instanceof SelfVariableAccess
) and
result = resolveConstantReadAccess(c.getAnArgument())
}
@@ -437,7 +437,7 @@ private module ResolveImpl {
encl = enclosingModule(this) and
result = [qualifiedModuleNameNonRec(encl, _, _), qualifiedModuleNameRec(encl, _, _)]
|
this.getReceiver() instanceof Self
this.getReceiver() instanceof SelfVariableAccess
or
not exists(this.getReceiver())
)

View File

@@ -184,9 +184,7 @@ abstract class ScopeImpl extends AstNode, TScopeType {
}
private class ScopeRealImpl extends ScopeImpl, TScopeReal {
private Scope::Range range;
ScopeRealImpl() { range = toGenerated(this) }
ScopeRealImpl() { toGenerated(this) instanceof Scope::Range }
override Variable getAVariableImpl() { result.getDeclaringScope() = this }
}

View File

@@ -366,7 +366,23 @@ private module Cached {
cached
predicate isCapturedAccess(LocalVariableAccess access) {
access.getVariable().getDeclaringScope() != access.getCfgScope()
exists(Scope scope1, Scope scope2 |
scope1 = access.getVariable().getDeclaringScope() and
scope2 = access.getCfgScope() and
scope1 != scope2
|
if access instanceof SelfVariableAccess
then
// ```
// class C
// def self.m // not a captured access
// end
// end
// ```
not scope2 instanceof Toplevel or
not access = any(SingletonMethod m).getObject()
else any()
)
}
cached
@@ -659,10 +675,11 @@ private class ClassVariableAccessSynth extends ClassVariableAccessRealImpl,
abstract class SelfVariableAccessImpl extends LocalVariableAccessImpl, TSelfVariableAccess { }
private class SelfVariableAccessReal extends SelfVariableAccessImpl, TSelfReal {
private Ruby::Self self;
private SelfVariable var;
SelfVariableAccessReal() { this = TSelfReal(self) and var = TSelfVariable(scopeOf(self)) }
SelfVariableAccessReal() {
exists(Ruby::Self self | this = TSelfReal(self) and var = TSelfVariable(scopeOf(self)))
}
final override SelfVariable getVariableImpl() { result = var }

View File

@@ -14,7 +14,7 @@ class EntryNode extends CfgNode, TEntryNode {
EntryNode() { this = TEntryNode(scope) }
final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
final override Location getLocation() { result = scope.getLocation() }
@@ -31,7 +31,7 @@ class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
/** Holds if this node represent a normal exit. */
final predicate isNormal() { normal = true }
final override AnnotatedExitBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() }
final override Location getLocation() { result = scope.getLocation() }

View File

@@ -1,4 +1,4 @@
private import ruby as rb
private import ruby as RB
private import ControlFlowGraphImpl as Impl
private import Completion as Comp
private import codeql.ruby.ast.internal.Synthesis
@@ -6,11 +6,11 @@ private import Splitting as Splitting
private import codeql.ruby.CFG as CFG
/** The base class for `ControlFlowTree`. */
class ControlFlowTreeBase extends rb::AstNode {
class ControlFlowTreeBase extends RB::AstNode {
ControlFlowTreeBase() { not any(Synthesis s).excludeFromControlFlowTree(this) }
}
class ControlFlowElement = rb::AstNode;
class ControlFlowElement = RB::AstNode;
class Completion = Comp::Completion;
@@ -69,6 +69,6 @@ predicate isAbnormalExitType(SuccessorType t) {
t instanceof CFG::SuccessorTypes::ExitSuccessor
}
class Location = rb::Location;
class Location = RB::Location;
class Node = CFG::CfgNode;

View File

@@ -233,6 +233,8 @@ module Ssa {
)
}
override SelfVariable getSourceVariable() { result = v }
final override string toString() { result = "self (" + v.getDeclaringScope() + ")" }
final override Location getLocation() { result = this.getControlFlowNode().getLocation() }
@@ -314,7 +316,7 @@ module Ssa {
CapturedCallDefinition() {
exists(Variable v, BasicBlock bb, int i |
this.definesAt(v, bb, i) and
SsaImpl::capturedCallWrite(bb, i, v)
SsaImpl::capturedCallWrite(_, bb, i, v)
)
}

View File

@@ -203,7 +203,7 @@ private module Cached {
result = lookupMethod(tp, method) and
if result.(Method).isPrivate()
then
exists(Self self |
exists(SelfVariableAccess self |
self = call.getReceiver().getExpr() and
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
pragma[only_bind_out](result.getEnclosingModule().getModule())
@@ -232,6 +232,18 @@ private module Cached {
)
}
/** Gets a viable run-time target for the call `call`. */
cached
DataFlowCallable viableCallable(DataFlowCall call) {
result = TCfgScope(getTarget(call.asCall())) and
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
or
exists(LibraryCallable callable |
result = TLibraryCallable(callable) and
call.asCall().getExpr() = callable.getACall()
)
}
cached
newtype TArgumentPosition =
TSelfArgumentPosition() or
@@ -300,28 +312,14 @@ private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) {
)
or
// `self` in method
exists(Self self, Method enclosing |
self = result.asExpr().getExpr() and
enclosing = self.getEnclosingMethod() and
tp = enclosing.getEnclosingModule().getModule() and
not self.getEnclosingModule().getEnclosingMethod() = enclosing
)
tp = result.(SsaSelfDefinitionNode).getSelfScope().(Method).getEnclosingModule().getModule()
or
// `self` in singleton method
exists(Self self, MethodBase enclosing |
self = result.asExpr().getExpr() and
flowsToSingletonMethodObject(trackInstance(tp), enclosing) and
enclosing = self.getEnclosingMethod() and
not self.getEnclosingModule().getEnclosingMethod() = enclosing
)
flowsToSingletonMethodObject(trackInstance(tp), result.(SsaSelfDefinitionNode).getSelfScope())
or
// `self` in top-level
exists(Self self, Toplevel enclosing |
self = result.asExpr().getExpr() and
enclosing = self.getEnclosingModule() and
tp = TResolved("Object") and
not self.getEnclosingMethod().getEnclosingModule() = enclosing
)
result.(SsaSelfDefinitionNode).getSelfScope() instanceof Toplevel and
tp = TResolved("Object")
or
// a module or class
exists(Module m |
@@ -371,7 +369,7 @@ private predicate singletonMethod(MethodBase method, Expr object) {
pragma[nomagic]
private predicate flowsToSingletonMethodObject(DataFlow::LocalSourceNode nodeFrom, MethodBase method) {
exists(DataFlow::LocalSourceNode nodeTo |
exists(DataFlow::Node nodeTo |
nodeFrom.flowsTo(nodeTo) and
singletonMethod(method, nodeTo.asExpr().getExpr())
)
@@ -409,13 +407,8 @@ private DataFlow::LocalSourceNode trackSingletonMethod(MethodBase m, string name
name = m.getName()
}
private DataFlow::Node selfInModule(Module tp) {
exists(Self self, ModuleBase enclosing |
self = result.asExpr().getExpr() and
enclosing = self.getEnclosingModule() and
tp = enclosing.getModule() and
not self.getEnclosingMethod().getEnclosingModule() = enclosing
)
private SsaSelfDefinitionNode selfInModule(Module tp) {
tp = result.getSelfScope().(ModuleBase).getModule()
}
private DataFlow::LocalSourceNode trackModule(Module tp, TypeTracker t) {
@@ -442,17 +435,6 @@ private DataFlow::LocalSourceNode trackModule(Module tp) {
result = trackModule(tp, TypeTracker::end())
}
/** Gets a viable run-time target for the call `call`. */
DataFlowCallable viableCallable(DataFlowCall call) {
result = TCfgScope(getTarget(call.asCall())) and
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
or
exists(LibraryCallable callable |
result = TLibraryCallable(callable) and
call.asCall().getExpr() = callable.getACall()
)
}
/**
* Holds if the set of viable implementations that can be called by `call`
* might be improved by knowing the call context. This is the case if the

View File

@@ -70,6 +70,20 @@ module LocalFlow {
)
}
/** Gets the SSA definition node corresponding to the implicit `self` parameter for `m`. */
private SsaDefinitionNode getSelfParameterDefNode(MethodBase m) {
result.getDefinition().(Ssa::SelfDefinition).getSourceVariable().getDeclaringScope() = m
}
/**
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
*/
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
nodeTo = getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
or
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
}
/**
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
* involving SSA definition `def`.
@@ -115,9 +129,6 @@ module LocalFlow {
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
localSsaFlowStep(nodeFrom, nodeTo)
or
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
nodeTo.asExpr().getExpr() instanceof Self
or
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
or
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
@@ -236,7 +247,7 @@ private module Cached {
or
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
or
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
or
@@ -253,7 +264,7 @@ private module Cached {
or
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
or
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
@@ -275,27 +286,34 @@ private module Cached {
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
}
private predicate entrySsaDefinition(SsaDefinitionNode n) {
n = LocalFlow::getParameterDefNode(_)
or
exists(Ssa::Definition def | def = n.getDefinition() |
def instanceof Ssa::SelfDefinition
or
def instanceof Ssa::CapturedEntryDefinition
)
}
cached
predicate isLocalSourceNode(Node n) {
n instanceof ParameterNode
or
// This case should not be needed once we have proper use-use flow
// for `self`. At that point, the `self`s returned by `trackInstance`
// in `DataFlowDispatch.qll` should refer to the post-update node,
// and we can remove this case.
n.asExpr().getExpr() instanceof Self
n instanceof PostUpdateNodes::ExprPostUpdateNode
or
// Nodes that can't be reached from another parameter or expression.
not localFlowStepTypeTracker+(any(Node e |
e instanceof ExprNode
// Expressions that can't be reached from another entry definition or expression.
not localFlowStepTypeTracker+(any(Node n0 |
n0 instanceof ExprNode
or
e instanceof ParameterNode
), n)
entrySsaDefinition(n0)
), n.(ExprNode))
or
// Ensure all parameter SSA nodes are local sources -- this is needed by type tracking.
// Note that when the parameter has a default value, it will be reachable from an
// expression (the default value) and therefore won't be caught by the rule above.
n = LocalFlow::getParameterDefNode(_)
// Ensure all entry SSA definitions are local sources -- for parameters, this
// is needed by type tracking. Note that when the parameter has a default value,
// it will be reachable from an expression (the default value) and therefore
// won't be caught by the rule above.
entrySsaDefinition(n)
}
cached
@@ -358,6 +376,16 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
override string toStringImpl() { result = def.toString() }
}
/** An SSA definition for a `self` variable. */
class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionNode {
private SelfVariable self;
SsaSelfDefinitionNode() { self = def.getSourceVariable() }
/** Gets the scope in which the `self` variable is declared. */
Scope getSelfScope() { result = self.getDeclaringScope() }
}
/**
* A value returning statement, viewed as a node in a data flow graph.
*
@@ -745,13 +773,6 @@ predicate jumpStep(Node pred, Node succ) {
SsaImpl::captureFlowOut(pred.(SsaDefinitionNode).getDefinition(),
succ.(SsaDefinitionNode).getDefinition())
or
exists(Self s, Method m |
s = succ.asExpr().getExpr() and
pred.(SelfParameterNode).getMethod() = m and
m = s.getEnclosingMethod() and
m != s.getEnclosingCallable()
)
or
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
}

View File

@@ -1,4 +1,5 @@
private import SsaImplCommon
private import SsaImplSpecific as SsaImplSpecific
private import codeql.ruby.AST
private import codeql.ruby.CFG
private import codeql.ruby.ast.Variable
@@ -40,58 +41,50 @@ private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVaria
i = bb.length()
}
private CfgScope getCaptureOuterCfgScope(CfgScope scope) {
result = scope.getOuterCfgScope() and
(
scope instanceof Block
or
scope instanceof Lambda
)
}
/** Holds if captured variable `v` is read inside `scope`. */
/**
* Holds if captured variable `v` is read directly inside `scope`,
* or inside a (transitively) nested scope of `scope`.
*/
pragma[noinline]
private predicate hasCapturedRead(Variable v, CfgScope scope) {
any(LocalVariableReadAccess read |
read.getVariable() = v and scope = getCaptureOuterCfgScope*(read.getCfgScope())
read.getVariable() = v and scope = read.getCfgScope().getOuterCfgScope*()
).isCapturedAccess()
}
/**
* Holds if `v` is written inside basic block `bb`, which is in the immediate
* outer scope of `scope`.
*/
pragma[noinline]
private predicate variableWriteInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) {
SsaImplSpecific::variableWrite(bb, _, v, _) and
scope.getOuterCfgScope() = bb.getScope()
}
pragma[noinline]
private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable v, CfgScope scope) {
hasCapturedRead(v, scope) and
exists(VariableWriteAccess write |
write = bb.getANode().getNode() and
write.getVariable() = v and
bb.getScope() = scope.getOuterCfgScope()
)
variableWriteInOuterScope(bb, v, scope)
}
/**
* Holds if the call at index `i` in basic block `bb` may reach a callable
* that reads captured variable `v`.
* Holds if the call `call` at index `i` in basic block `bb` may reach
* a callable that reads captured variable `v`.
*/
private predicate capturedCallRead(BasicBlock bb, int i, LocalVariable v) {
private predicate capturedCallRead(Call call, BasicBlock bb, int i, LocalVariable v) {
exists(CfgScope scope |
hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and
bb.getNode(i).getNode() instanceof Call
call = bb.getNode(i).getNode()
|
not scope instanceof Block
or
// If the read happens inside a block, we restrict to the call that
// contains the block
scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock()
not scope instanceof Block
or
scope = call.(MethodCall).getBlock()
)
}
/** Holds if captured variable `v` is written inside `scope`. */
pragma[noinline]
private predicate hasCapturedWrite(Variable v, CfgScope scope) {
any(LocalVariableWriteAccess write |
write.getVariable() = v and scope = getCaptureOuterCfgScope*(write.getCfgScope())
).isCapturedAccess()
}
/** Holds if `v` is read at index `i` in basic block `bb`. */
private predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) {
exists(VariableReadAccess read |
@@ -104,21 +97,38 @@ predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) {
variableReadActual(bb, i, v) and
certain = true
or
capturedCallRead(bb, i, v) and
capturedCallRead(_, bb, i, v) and
certain = false
or
capturedExitRead(bb, i, v) and
certain = false
}
/**
* Holds if captured variable `v` is written directly inside `scope`,
* or inside a (transitively) nested scope of `scope`.
*/
pragma[noinline]
private predicate hasCapturedWrite(Variable v, CfgScope scope) {
any(LocalVariableWriteAccess write |
write.getVariable() = v and scope = write.getCfgScope().getOuterCfgScope*()
).isCapturedAccess()
}
/**
* Holds if `v` is read inside basic block `bb`, which is in the immediate
* outer scope of `scope`.
*/
pragma[noinline]
private predicate variableReadActualInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) {
variableReadActual(bb, _, v) and
bb.getScope() = scope.getOuterCfgScope()
}
pragma[noinline]
private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) {
hasCapturedWrite(v, scope) and
exists(VariableReadAccess read |
read = bb.getANode().getNode() and
read.getVariable() = v and
bb.getScope() = scope.getOuterCfgScope()
)
variableReadActualInOuterScope(bb, v, scope)
}
cached
@@ -134,20 +144,20 @@ private module Cached {
}
/**
* Holds if the call at index `i` in basic block `bb` may reach a callable
* Holds if the call `call` at index `i` in basic block `bb` may reach a callable
* that writes captured variable `v`.
*/
cached
predicate capturedCallWrite(BasicBlock bb, int i, LocalVariable v) {
predicate capturedCallWrite(Call call, BasicBlock bb, int i, LocalVariable v) {
exists(CfgScope scope |
hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and
bb.getNode(i).getNode() instanceof Call
call = bb.getNode(i).getNode()
|
not scope instanceof Block
or
// If the write happens inside a block, we restrict to the call that
// contains the block
scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock()
not scope instanceof Block
or
scope = call.(MethodCall).getBlock()
)
}
@@ -177,6 +187,26 @@ private module Cached {
)
}
pragma[noinline]
private predicate defReachesCallReadInOuterScope(
Definition def, Call call, LocalVariable v, CfgScope scope
) {
exists(BasicBlock bb, int i |
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, CfgScope scope) {
exists(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
@@ -188,13 +218,35 @@ private module Cached {
*/
cached
predicate captureFlowIn(Definition def, Definition entry) {
exists(LocalVariable v, BasicBlock bb, int i |
exists(Call call, LocalVariable v, 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.(MethodCall).getBlock()
)
}
private import codeql.ruby.dataflow.SSA
pragma[noinline]
private predicate defReachesExitReadInInnerScope(Definition def, LocalVariable v, CfgScope scope) {
exists(BasicBlock bb, int i |
ssaDefReachesRead(v, def, bb, i) and
capturedCallRead(bb, i, v) and
exists(BasicBlock bb2, int i2 |
capturedEntryWrite(bb2, i2, v) and
entry.definesAt(v, bb2, i2)
)
capturedExitRead(bb, i, v) and
scope = bb.getScope().getOuterCfgScope*()
)
}
pragma[noinline]
private predicate hasCapturedExitRead(Definition exit, Call call, LocalVariable v, CfgScope scope) {
exists(BasicBlock bb, int i |
capturedCallWrite(call, bb, i, v) and
exit.definesAt(v, bb, i) and
bb.getScope() = scope.getOuterCfgScope()
)
}
@@ -210,13 +262,15 @@ private module Cached {
*/
cached
predicate captureFlowOut(Definition def, Definition exit) {
exists(LocalVariable v, BasicBlock bb, int i |
ssaDefReachesRead(v, def, bb, i) and
capturedExitRead(bb, i, v) and
exists(BasicBlock bb2, int i2 |
capturedCallWrite(bb2, i2, v) and
exit.definesAt(v, bb2, i2)
)
exists(Call call, LocalVariable v, 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.(MethodCall).getBlock()
)
}

View File

@@ -287,20 +287,6 @@ private module SsaDefReaches {
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition
* `redef` in the same basic block, without crossing another SSA definition of `v`.
*/
predicate ssaDefReachesUncertainDefWithinBlock(
SourceVariable v, Definition def, UncertainWriteDefinition redef
) {
exists(BasicBlock bb, int rnk, int i |
ssaDefReachesRank(bb, def, rnk, v) and
rnk = ssaRefRank(bb, i, v, SsaDef()) - 1 and
redef.definesAt(v, bb, i)
)
}
/**
* Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`.
*/

View File

@@ -40,7 +40,7 @@ predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain)
) and
certain = true
or
SsaImpl::capturedCallWrite(bb, i, v) and
SsaImpl::capturedCallWrite(_, bb, i, v) and
certain = false
}

View File

@@ -66,35 +66,53 @@ private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
)
}
/**
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
* in all global taint flow configurations.
*/
cached
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// value of `case` expression into variables in patterns
exists(CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprNodes::InClauseCfgNode clause |
nodeFrom.asExpr() = case.getValue() and
clause = case.getBranch(_) and
nodeTo.(SsaDefinitionNode).getDefinition().getControlFlowNode() =
variablesInPattern(clause.getPattern())
)
or
// operation involving `nodeFrom`
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
not op.getExpr() instanceof AssignExpr
)
or
// string interpolation of `nodeFrom` into `nodeTo`
nodeFrom.asExpr() =
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
or
// Although flow through arrays is modelled precisely using stores/reads, we still
// allow flow out of a _tainted_ array. This is needed in order to support taint-
// tracking configurations where the source is an array.
readStep(nodeFrom, any(DataFlow::Content::ArrayElementContent c), nodeTo)
private module Cached {
/**
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
* in all global taint flow configurations.
*/
cached
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// value of `case` expression into variables in patterns
exists(CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprNodes::InClauseCfgNode clause |
nodeFrom.asExpr() = case.getValue() and
clause = case.getBranch(_) and
nodeTo.(SsaDefinitionNode).getDefinition().getControlFlowNode() =
variablesInPattern(clause.getPattern())
)
or
// operation involving `nodeFrom`
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
not op.getExpr() instanceof AssignExpr
)
or
// string interpolation of `nodeFrom` into `nodeTo`
nodeFrom.asExpr() =
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
or
// Although flow through arrays is modelled precisely using stores/reads, we still
// allow flow out of a _tainted_ array. This is needed in order to support taint-
// tracking configurations where the source is an array.
readStep(nodeFrom, any(DataFlow::Content::ArrayElementContent c), nodeTo)
}
/**
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
*/
cached
predicate localTaintStepCached(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
defaultAdditionalTaintStep(nodeFrom, nodeTo)
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, false)
}
}
import Cached

View File

@@ -20,14 +20,4 @@ predicate localExprTaint(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
}
/**
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
*/
predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
defaultAdditionalTaintStep(nodeFrom, nodeTo)
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, false)
}
predicate localTaintStep = localTaintStepCached/2;

View File

@@ -64,13 +64,30 @@ abstract class Configuration extends DataFlow::Configuration {
override predicate isSource(DataFlow::Node source) { none() }
/**
* Holds if `sink` is a relevant taint sink.
* Holds if `source` is a relevant taint source with the given initial
* `state`.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) { none() }
/**
* Holds if `sink` is a relevant taint sink
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
/**
* Holds if `sink` is a relevant taint sink accepting `state`.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { none() }
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
@@ -79,6 +96,16 @@ abstract class Configuration extends DataFlow::Configuration {
defaultTaintSanitizer(node)
}
/**
* Holds if the node `node` is a taint sanitizer when the flow state is
* `state`.
*/
predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) { none() }
final override predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
this.isSanitizer(node, state)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
@@ -107,6 +134,25 @@ abstract class Configuration extends DataFlow::Configuration {
defaultAdditionalTaintStep(node1, node2)
}
/**
* Holds if the additional taint propagation step from `node1` to `node2`
* must be taken into account in the analysis. This step is only applicable
* in `state1` and updates the flow state to `state2`.
*/
predicate isAdditionalTaintStep(
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
DataFlow::FlowState state2
) {
none()
}
final override predicate isAdditionalFlowStep(
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
DataFlow::FlowState state2
) {
this.isAdditionalTaintStep(node1, state1, node2, state2)
}
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::Content c) {
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
defaultImplicitTaintRead(node, c)

View File

@@ -105,7 +105,7 @@ private class ActionControllerContextCall extends MethodCall {
private ActionControllerControllerClass controllerClass;
ActionControllerContextCall() {
this.getReceiver() instanceof Self and
this.getReceiver() instanceof SelfVariableAccess and
this.getEnclosingModule() = controllerClass
}
@@ -127,9 +127,7 @@ abstract class ParamsCall extends MethodCall {
* ActionController parameters available via the `params` method.
*/
class ParamsSource extends RemoteFlowSource::Range {
ParamsCall call;
ParamsSource() { this.asExpr().getExpr() = call }
ParamsSource() { this.asExpr().getExpr() instanceof ParamsCall }
override string getSourceType() { result = "ActionController::Metal#params" }
}
@@ -146,9 +144,7 @@ abstract class CookiesCall extends MethodCall {
* ActionController parameters available via the `cookies` method.
*/
class CookiesSource extends RemoteFlowSource::Range {
CookiesCall call;
CookiesSource() { this.asExpr().getExpr() = call }
CookiesSource() { this.asExpr().getExpr() instanceof CookiesCall }
override string getSourceType() { result = "ActionController::Metal#cookies" }
}

View File

@@ -573,15 +573,16 @@ module ActionDispatch {
*/
private class ResourcesRoute extends RouteImpl, TResourcesRoute {
RouteBlock parent;
string resource;
string action;
string httpMethod;
string pathComponent;
ResourcesRoute() {
this = TResourcesRoute(parent, method, action) and
method.getArgument(0).getConstantValue().isStringOrSymbol(resource) and
isDefaultResourceRoute(resource, httpMethod, pathComponent, action)
exists(string resource |
this = TResourcesRoute(parent, method, action) and
method.getArgument(0).getConstantValue().isStringOrSymbol(resource) and
isDefaultResourceRoute(resource, httpMethod, pathComponent, action)
)
}
override string getAPrimaryQlClass() { result = "ResourcesRoute" }
@@ -610,15 +611,16 @@ module ActionDispatch {
*/
private class SingularResourceRoute extends RouteImpl, TResourceRoute {
RouteBlock parent;
string resource;
string action;
string httpMethod;
string pathComponent;
SingularResourceRoute() {
this = TResourceRoute(parent, method, action) and
method.getArgument(0).getConstantValue().isStringOrSymbol(resource) and
isDefaultSingularResourceRoute(resource, httpMethod, pathComponent, action)
exists(string resource |
this = TResourceRoute(parent, method, action) and
method.getArgument(0).getConstantValue().isStringOrSymbol(resource) and
isDefaultSingularResourceRoute(resource, httpMethod, pathComponent, action)
)
}
override string getAPrimaryQlClass() { result = "SingularResourceRoute" }

View File

@@ -61,7 +61,7 @@ private class ActionViewHtmlEscapeCall extends HtmlEscapeCall {
// A call in a context where some commonly used `ActionView` methods are available.
private class ActionViewContextCall extends MethodCall {
ActionViewContextCall() {
this.getReceiver() instanceof Self and
this.getReceiver() instanceof SelfVariableAccess and
inActionViewContext(this)
}

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.dataflow.internal.DataFlowPrivate
private import codeql.ruby.ast.internal.Module
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.Stdlib
@@ -100,7 +101,7 @@ class ActiveRecordModelClassMethodCall extends MethodCall {
recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass()
or
// e.g. self.where(...) within an ActiveRecordModelClass
this.getReceiver() instanceof Self and
this.getReceiver() instanceof SelfVariableAccess and
this.getEnclosingModule() = recvCls
}
@@ -268,29 +269,30 @@ private Expr getUltimateReceiver(MethodCall call) {
// A call to `find`, `where`, etc. that may return active record model object(s)
private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation, DataFlow::CallNode {
private MethodCall call;
private ActiveRecordModelClass cls;
private Expr recv;
ActiveRecordModelFinderCall() {
call = this.asExpr().getExpr() and
recv = getUltimateReceiver(call) and
resolveConstant(recv) = cls.getAQualifiedName() and
call.getMethodName() = finderMethodName()
exists(MethodCall call, Expr recv |
call = this.asExpr().getExpr() and
recv = getUltimateReceiver(call) and
resolveConstant(recv) = cls.getAQualifiedName() and
call.getMethodName() = finderMethodName()
)
}
final override ActiveRecordModelClass getClass() { result = cls }
}
// A `self` reference that may resolve to an active record model object
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
SsaSelfDefinitionNode {
private ActiveRecordModelClass cls;
ActiveRecordModelClassSelfReference() {
exists(Self s |
s.getEnclosingModule() = cls and
s.getEnclosingMethod() = cls.getAMethod() and
s = this.asExpr().getExpr()
exists(MethodBase m |
m = this.getCfgScope() and
m.getEnclosingModule() = cls and
m = cls.getAMethod()
)
}
@@ -314,3 +316,146 @@ private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
ActiveRecordInstance getInstance() { result = instance }
}
/**
* Provides modeling relating to the `ActiveRecord::Persistence` module.
*/
private module Persistence {
/**
* Holds if there is a hash literal argument to `call` at `argIndex`
* containing a KV pair with value `value`.
*/
private predicate hashArgumentWithValue(
DataFlow::CallNode call, int argIndex, DataFlow::ExprNode value
) {
exists(ExprNodes::HashLiteralCfgNode hash, ExprNodes::PairCfgNode pair |
hash = call.getArgument(argIndex).asExpr() and
pair = hash.getAKeyValuePair()
|
value.asExpr() = pair.getValue()
)
}
/**
* Holds if `call` has a keyword argument of with value `value`.
*/
private predicate keywordArgumentWithValue(DataFlow::CallNode call, DataFlow::ExprNode value) {
exists(ExprNodes::PairCfgNode pair | pair = call.getArgument(_).asExpr() |
value.asExpr() = pair.getValue()
)
}
/** A call to e.g. `User.create(name: "foo")` */
private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
CreateLikeCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() =
[
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
"find_or_create_by!", "insert", "insert!"
]
}
override DataFlow::Node getValue() {
// attrs as hash elements in arg0
hashArgumentWithValue(this, 0, result) or
keywordArgumentWithValue(this, result)
}
}
/** A call to e.g. `User.update(1, name: "foo")` */
private class UpdateLikeClassMethodCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
UpdateLikeClassMethodCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = ["update", "update!", "upsert"]
}
override DataFlow::Node getValue() {
keywordArgumentWithValue(this, result)
or
// Case where 2 array args are passed - the first an array of IDs, and the
// second an array of hashes - each hash corresponding to an ID in the
// first array.
exists(ExprNodes::ArrayLiteralCfgNode hashesArray |
this.getArgument(0).asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
hashesArray = this.getArgument(1).asExpr()
|
exists(ExprNodes::HashLiteralCfgNode hash, ExprNodes::PairCfgNode pair |
hash = hashesArray.getArgument(_) and
pair = hash.getAKeyValuePair()
|
result.asExpr() = pair.getValue()
)
)
}
}
/** A call to e.g. `User.insert_all([{name: "foo"}, {name: "bar"}])` */
private class InsertAllLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
private ExprNodes::ArrayLiteralCfgNode arr;
InsertAllLikeCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = ["insert_all", "insert_all!", "upsert_all"] and
arr = this.getArgument(0).asExpr()
}
override DataFlow::Node getValue() {
// attrs as hash elements of members of array arg0
exists(ExprNodes::HashLiteralCfgNode hash, ExprNodes::PairCfgNode pair |
hash = arr.getArgument(_) and
pair = hash.getAKeyValuePair()
|
result.asExpr() = pair.getValue()
)
}
}
/** A call to e.g. `user.update(name: "foo")` */
private class UpdateLikeInstanceMethodCall extends PersistentWriteAccess::Range,
ActiveRecordInstanceMethodCall {
UpdateLikeInstanceMethodCall() {
this.getMethodName() = ["update", "update!", "update_attributes", "update_attributes!"]
}
override DataFlow::Node getValue() {
// attrs as hash elements in arg0
hashArgumentWithValue(this, 0, result)
or
// keyword arg
keywordArgumentWithValue(this, result)
}
}
/** A call to e.g. `user.update_attribute(name, "foo")` */
private class UpdateAttributeCall extends PersistentWriteAccess::Range,
ActiveRecordInstanceMethodCall {
UpdateAttributeCall() { this.getMethodName() = "update_attribute" }
override DataFlow::Node getValue() {
// e.g. `foo.update_attribute(key, value)`
result = this.getArgument(1)
}
}
/**
* An assignment like `user.name = "foo"`. Though this does not write to the
* database without a subsequent call to persist the object, it is considered
* as an `PersistentWriteAccess` to avoid missing cases where the path to a
* subsequent write is not clear.
*/
private class AssignAttribute extends PersistentWriteAccess::Range {
private ExprNodes::AssignExprCfgNode assignNode;
AssignAttribute() {
exists(DataFlow::CallNode setter |
assignNode = this.asExpr() and
setter.getArgument(0) = this and
setter instanceof ActiveRecordInstanceMethodCall and
setter.asExpr().getExpr() instanceof SetterMethodCall
)
}
override DataFlow::Node getValue() { assignNode.getRhs() = result.asExpr() }
}
}

View File

@@ -69,7 +69,10 @@ abstract private class IOOrFileMethodCall extends DataFlow::CallNode {
}
/** Gets the API used to perform this call, either "IO" or "File" */
abstract string getAPI();
abstract string getApi();
/** DEPRECATED: Alias for getApi */
deprecated string getAPI() { result = this.getApi() }
/** Gets a node representing the data read or written by this call */
abstract DataFlow::Node getADataNodeImpl();
@@ -110,7 +113,10 @@ private class IOOrFileReadMethodCall extends IOOrFileMethodCall {
)
}
override string getAPI() { result = api }
override string getApi() { result = api }
/** DEPRECATED: Alias for getApi */
deprecated override string getAPI() { result = this.getApi() }
override DataFlow::Node getADataNodeImpl() { result = this }
@@ -151,7 +157,10 @@ private class IOOrFileWriteMethodCall extends IOOrFileMethodCall {
)
}
override string getAPI() { result = api }
override string getApi() { result = api }
/** DEPRECATED: Alias for getApi */
deprecated override string getAPI() { result = this.getApi() }
override DataFlow::Node getADataNodeImpl() { result = dataNode }
@@ -180,12 +189,6 @@ module IO {
}
}
// "Direct" `IO` instances, i.e. cases where there is no more specific
// subtype such as `File`
private class IOInstanceStrict extends IOInstance {
IOInstanceStrict() { this = ioInstance() }
}
/**
* A `DataFlow::CallNode` that reads data using the `IO` class. For example,
* the `read` and `readline` calls in:
@@ -202,7 +205,7 @@ module IO {
* that use a subclass of `IO` such as `File`.
*/
class IOReader extends IOOrFileReadMethodCall {
IOReader() { this.getAPI() = "IO" }
IOReader() { this.getApi() = "IO" }
}
/**
@@ -221,7 +224,7 @@ module IO {
* that use a subclass of `IO` such as `File`.
*/
class IOWriter extends IOOrFileWriteMethodCall {
IOWriter() { this.getAPI() = "IO" }
IOWriter() { this.getApi() = "IO" }
}
/**
@@ -306,7 +309,7 @@ module File {
* ```
*/
class FileModuleReader extends IO::FileReader {
FileModuleReader() { this.getAPI() = "File" }
FileModuleReader() { this.getApi() = "File" }
override DataFlow::Node getADataNode() { result = this.getADataNodeImpl() }

View File

@@ -221,7 +221,7 @@ private class GraphqlSchemaObjectClassMethodCall extends MethodCall {
recvCls.getModule() = resolveConstantReadAccess(this.getReceiver())
or
// e.g. self.some_method(...) within a graphql Object or Interface
this.getReceiver() instanceof Self and
this.getReceiver() instanceof SelfVariableAccess and
this.getEnclosingModule() = recvCls
}

View File

@@ -18,20 +18,15 @@ module Kernel {
* providing a specific receiver as in `Kernel.exit`.
*/
class KernelMethodCall extends DataFlow::CallNode {
private MethodCall methodCall;
KernelMethodCall() {
methodCall = this.asExpr().getExpr() and
this = API::getTopLevelMember("Kernel").getAMethodCall(_)
or
this.asExpr().getExpr() instanceof UnknownMethodCall and
(
this = API::getTopLevelMember("Kernel").getAMethodCall(_)
this.getReceiver().asExpr().getExpr() instanceof SelfVariableAccess and
isPrivateKernelMethod(this.getMethodName())
or
methodCall instanceof UnknownMethodCall and
(
this.getReceiver().asExpr().getExpr() instanceof Self and
isPrivateKernelMethod(methodCall.getMethodName())
or
isPublicKernelMethod(methodCall.getMethodName())
)
isPublicKernelMethod(this.getMethodName())
)
}
}

View File

@@ -176,8 +176,8 @@ private module RegexpMatching {
}
/** A class to test whether a regular expression matches certain HTML tags. */
class HTMLMatchingRegExp extends RegexpMatching::MatchedRegExp {
HTMLMatchingRegExp() {
class HtmlMatchingRegExp extends RegexpMatching::MatchedRegExp {
HtmlMatchingRegExp() {
// the regexp must mention "<" and ">" explicitly.
forall(string angleBracket | angleBracket = ["<", ">"] |
any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
@@ -204,12 +204,15 @@ class HTMLMatchingRegExp extends RegexpMatching::MatchedRegExp {
}
}
/** DEPRECATED: Alias for HtmlMatchingRegExp */
deprecated class HTMLMatchingRegExp = HtmlMatchingRegExp;
/**
* Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
*
* When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
*/
predicate isBadRegexpFilter(HTMLMatchingRegExp regexp, string msg) {
predicate isBadRegexpFilter(HtmlMatchingRegExp regexp, string msg) {
// CVE-2021-33829 - matching both "<!-- foo -->" and "<!-- foo --!>", but in different capture groups
regexp.matches("<!-- foo -->") and
regexp.matches("<!-- foo --!>") and

View File

@@ -13,8 +13,8 @@ import codeql.ruby.TaintTracking
/**
* Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
*/
module ReflectedXSS {
import XSS::ReflectedXSS
module ReflectedXss {
import XSS::ReflectedXss
/**
* A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
@@ -33,7 +33,10 @@ module ReflectedXSS {
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalXSSTaintStep(node1, node2)
isAdditionalXssTaintStep(node1, node2)
}
}
}
/** DEPRECATED: Alias for ReflectedXss */
deprecated module ReflectedXSS = ReflectedXss;

View File

@@ -11,8 +11,9 @@ import ruby
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
module StoredXSS {
import XSS::StoredXSS
/** Provides a taint-tracking configuration for cross-site scripting vulnerabilities. */
module StoredXss {
import XSS::StoredXss
/**
* A taint-tracking configuration for reasoning about Stored XSS.
@@ -34,7 +35,10 @@ module StoredXSS {
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalXSSTaintStep(node1, node2)
isAdditionalXssTaintStep(node1, node2)
}
}
}
/** DEPRECATED: Alias for StoredXss */
deprecated module StoredXSS = StoredXss;

View File

@@ -245,7 +245,7 @@ private module Shared {
/**
* An additional step that is preserves dataflow in the context of XSS.
*/
predicate isAdditionalXSSFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
predicate isAdditionalXssFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
isFlowFromLocals(node1, node2)
or
isFlowFromControllerInstanceVariable(node1, node2)
@@ -254,6 +254,9 @@ private module Shared {
or
isFlowFromHelperMethod(node1, node2)
}
/** DEPRECATED: Alias for isAdditionalXssFlowStep */
deprecated predicate isAdditionalXSSFlowStep = isAdditionalXssFlowStep/2;
}
/**
@@ -261,7 +264,7 @@ private module Shared {
* "reflected cross-site scripting" vulnerabilities, as well as
* extension points for adding your own.
*/
module ReflectedXSS {
module ReflectedXss {
/** A data flow source for stored XSS vulnerabilities. */
abstract class Source extends Shared::Source { }
@@ -277,7 +280,10 @@ module ReflectedXSS {
/**
* An additional step that is preserves dataflow in the context of reflected XSS.
*/
predicate isAdditionalXSSTaintStep = Shared::isAdditionalXSSFlowStep/2;
predicate isAdditionalXssTaintStep = Shared::isAdditionalXssFlowStep/2;
/** DEPRECATED: Alias for isAdditionalXssTaintStep */
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
/**
* A source of remote user input, considered as a flow source.
@@ -285,6 +291,9 @@ module ReflectedXSS {
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
}
/** DEPRECATED: Alias for ReflectedXss */
deprecated module ReflectedXSS = ReflectedXss;
private module OrmTracking {
/**
* A data flow configuration to track flow from finder calls to field accesses.
@@ -298,7 +307,7 @@ private module OrmTracking {
override predicate isSink(DataFlow2::Node sink) { sink instanceof DataFlow2::CallNode }
override predicate isAdditionalFlowStep(DataFlow2::Node node1, DataFlow2::Node node2) {
Shared::isAdditionalXSSFlowStep(node1, node2)
Shared::isAdditionalXssFlowStep(node1, node2)
or
// Propagate flow through arbitrary method calls
node2.(DataFlow2::CallNode).getReceiver() = node1
@@ -309,7 +318,8 @@ private module OrmTracking {
}
}
module StoredXSS {
/** Provides default sources, sinks and sanitizers for detecting stored cross-site scripting (XSS) vulnerabilities. */
module StoredXss {
/** A data flow source for stored XSS vulnerabilities. */
abstract class Source extends Shared::Source { }
@@ -325,7 +335,10 @@ module StoredXSS {
/**
* An additional step that preserves dataflow in the context of stored XSS.
*/
predicate isAdditionalXSSTaintStep = Shared::isAdditionalXSSFlowStep/2;
predicate isAdditionalXssTaintStep = Shared::isAdditionalXssFlowStep/2;
/** DEPRECATED: Alias for isAdditionalXssTaintStep */
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
private class OrmFieldAsSource extends Source instanceof DataFlow2::CallNode {
OrmFieldAsSource() {
@@ -341,3 +354,6 @@ module StoredXSS {
private class FileSystemReadAccessAsSource extends Source instanceof FileSystemReadAccess { }
// TODO: Consider `FileNameSource` flowing to script tag `src` attributes and similar
}
/** DEPRECATED: Alias for StoredXss */
deprecated module StoredXSS = StoredXss;

View File

@@ -402,7 +402,8 @@ abstract class RegExp extends AST::StringlikeLiteral {
not exists(int x, int y | this.backreference(x, y) and x <= start and y >= end) and
not exists(int x, int y |
this.pStyleNamedCharacterProperty(x, y, _) and x <= start and y >= end
)
) and
not exists(int x, int y | this.multiples(x, y, _, _) and x <= start and y >= end)
}
predicate normalCharacter(int start, int end) {
@@ -488,7 +489,7 @@ abstract class RegExp extends AST::StringlikeLiteral {
this.group(start, end) and
exists(int nameEnd |
this.namedGroupStart(start, nameEnd) and
result = this.getText().substring(start + 4, nameEnd - 1)
result = this.getText().substring(start + 3, nameEnd - 1)
)
}
@@ -861,6 +862,7 @@ abstract class RegExp extends AST::StringlikeLiteral {
* Whether the text in the range start,end is an alternation
*/
predicate alternation(int start, int end) {
not this.inCharSet(start) and
this.topLevel(start, end) and
exists(int less | this.subalternation(start, less, _) and less < end)
}

View File

@@ -119,18 +119,18 @@ class EmptyPositiveSubPatttern extends RegExpSubPattern {
* whose root node is not a disjunction.
*/
class RegExpRoot extends RegExpTerm {
RegExpParent parent;
RegExpRoot() {
exists(RegExpAlt alt |
alt.isRootTerm() and
this = alt.getAChild() and
parent = alt.getParent()
exists(RegExpParent parent |
exists(RegExpAlt alt |
alt.isRootTerm() and
this = alt.getAChild() and
parent = alt.getParent()
)
or
this.isRootTerm() and
not this instanceof RegExpAlt and
parent = this.getParent()
)
or
this.isRootTerm() and
not this instanceof RegExpAlt and
parent = this.getParent()
}
/**
@@ -466,13 +466,14 @@ private module CharacterClasses {
* An implementation of `CharacterClass` for \d, \s, and \w.
*/
private class PositiveCharacterClassEscape extends CharacterClass {
RegExpTerm cc;
string charClass;
PositiveCharacterClassEscape() {
isEscapeClass(cc, charClass) and
this = getCanonicalCharClass(cc) and
charClass = ["d", "s", "w"]
exists(RegExpTerm cc |
isEscapeClass(cc, charClass) and
this = getCanonicalCharClass(cc) and
charClass = ["d", "s", "w"]
)
}
override string getARelevantChar() {
@@ -504,13 +505,14 @@ private module CharacterClasses {
* An implementation of `CharacterClass` for \D, \S, and \W.
*/
private class NegativeCharacterClassEscape extends CharacterClass {
RegExpTerm cc;
string charClass;
NegativeCharacterClassEscape() {
isEscapeClass(cc, charClass) and
this = getCanonicalCharClass(cc) and
charClass = ["D", "S", "W"]
exists(RegExpTerm cc |
isEscapeClass(cc, charClass) and
this = getCanonicalCharClass(cc) and
charClass = ["D", "S", "W"]
)
}
override string getARelevantChar() {

View File

@@ -1,5 +1,6 @@
private import codeql.ruby.ast.Literal as AST
private import ParseRegExp
private import codeql.NumberUtils
import codeql.Locations
private import codeql.ruby.DataFlow
@@ -82,7 +83,7 @@ class RegExpParent extends TRegExpParent {
RegExpTerm getChild(int i) { none() }
RegExpTerm getAChild() { result = this.getChild(_) }
final RegExpTerm getAChild() { result = this.getChild(_) }
int getNumChild() { result = count(this.getAChild()) }
@@ -254,12 +255,11 @@ newtype TRegExpParent =
class RegExpQuantifier extends RegExpTerm, TRegExpQuantifier {
int part_end;
boolean maybe_empty;
boolean may_repeat_forever;
RegExpQuantifier() {
this = TRegExpQuantifier(re, start, end) and
re.qualifiedPart(start, part_end, end, maybe_empty, may_repeat_forever)
re.qualifiedPart(start, part_end, end, _, may_repeat_forever)
}
override RegExpTerm getChild(int i) {
@@ -420,7 +420,9 @@ class RegExpEscape extends RegExpNormalChar {
result = this.getUnicode()
}
predicate isIdentityEscape() { not this.getUnescaped() in ["n", "r", "t"] }
predicate isIdentityEscape() {
not this.getUnescaped() in ["n", "r", "t"] and not this.isUnicode()
}
/**
* Gets the text for this escape. That is e.g. "\w".
@@ -437,21 +439,8 @@ class RegExpEscape extends RegExpNormalChar {
* E.g. for `\u0061` this returns "a".
*/
private string getUnicode() {
exists(int codepoint | codepoint = sum(this.getHexValueFromUnicode(_)) |
result = codepoint.toUnicode()
)
}
/**
* Gets int value for the `index`th char in the hex number of the unicode escape.
* E.g. for `\u0061` and `index = 2` this returns 96 (the number `6` interpreted as hex).
*/
private int getHexValueFromUnicode(int index) {
this.isUnicode() and
exists(string hex, string char | hex = this.getText().suffix(2) |
char = hex.charAt(index) and
result = 16.pow(hex.length() - index - 1) * toHex(char)
)
result = parseHexInt(this.getText().suffix(2)).toUnicode()
}
string getUnescaped() { result = this.getText().suffix(1) }
@@ -459,26 +448,6 @@ class RegExpEscape extends RegExpNormalChar {
override string getAPrimaryQlClass() { result = "RegExpEscape" }
}
/**
* Gets the hex number for the `hex` char.
*/
private int toHex(string hex) {
hex = [0 .. 9].toString() and
result = hex.toInt()
or
result = 10 and hex = ["a", "A"]
or
result = 11 and hex = ["b", "B"]
or
result = 12 and hex = ["c", "C"]
or
result = 13 and hex = ["d", "D"]
or
result = 14 and hex = ["e", "E"]
or
result = 15 and hex = ["f", "F"]
}
/**
* A word boundary, that is, a regular expression term of the form `\b`.
*/

View File

@@ -34,7 +34,8 @@ private module Cached {
CallStep() or
ReturnStep() or
StoreStep(ContentName content) or
LoadStep(ContentName content)
LoadStep(ContentName content) or
JumpStep()
/** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
cached
@@ -49,6 +50,9 @@ private module Cached {
step = LoadStep(content) and result = MkTypeTracker(hasCall, "")
or
exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p))
or
step = JumpStep() and
result = MkTypeTracker(false, content)
)
}
@@ -67,6 +71,9 @@ private module Cached {
)
or
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
or
step = JumpStep() and
result = MkTypeBackTracker(false, content)
)
}
@@ -110,12 +117,17 @@ class StepSummary extends TStepSummary {
exists(string content | this = StoreStep(content) | result = "store " + content)
or
exists(string content | this = LoadStep(content) | result = "load " + content)
or
this instanceof JumpStep and result = "jump"
}
}
pragma[noinline]
private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
jumpStep(nodeFrom, nodeTo) and
summary = JumpStep()
or
levelStep(nodeFrom, nodeTo) and
summary = LevelStep()
or
exists(string content |

View File

@@ -11,10 +11,32 @@ class Node = DataFlowPublic::Node;
class TypeTrackingNode = DataFlowPublic::LocalSourceNode;
/** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */
predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
/**
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
*/
predicate jumpStep = DataFlowPrivate::jumpStep/2;
/**
* Holds if there is a summarized local flow step from `nodeFrom` to `nodeTo`,
* because there is direct flow from a parameter to a return. That is, summarized
* steps are not applied recursively.
*/
pragma[nomagic]
private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
exists(DataFlowPublic::ParameterNode param, DataFlowPrivate::ReturningNode returnNode |
DataFlowPrivate::LocalFlow::getParameterDefNode(param.getParameter())
.(TypeTrackingNode)
.flowsTo(returnNode) and
callStep(nodeTo.asExpr(), nodeFrom, param)
)
}
/** Holds if there is a level step from `nodeFrom` to `nodeTo`. */
predicate levelStep(Node nodeFrom, Node nodeTo) { summarizedLocalStep(nodeFrom, nodeTo) }
/**
* Gets the name of a possible piece of content. This will usually include things like
*
@@ -45,6 +67,13 @@ private predicate viableParam(
)
}
private predicate callStep(ExprNodes::CallCfgNode call, Node nodeFrom, Node nodeTo) {
exists(DataFlowDispatch::ParameterPosition pos |
argumentPositionMatch(call, nodeFrom, pos) and
viableParam(call, nodeTo, pos)
)
}
/**
* Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
*
@@ -53,19 +82,13 @@ private predicate viableParam(
* methods is done using API graphs (which uses type tracking).
*/
predicate callStep(Node nodeFrom, Node nodeTo) {
exists(ExprNodes::CallCfgNode call, DataFlowDispatch::ParameterPosition pos |
argumentPositionMatch(call, nodeFrom, pos) and
viableParam(call, nodeTo, pos)
)
callStep(_, nodeFrom, nodeTo)
or
// In normal data-flow, this will be a local flow step. But for type tracking
// we model it as a call step, in order to avoid computing a potential
// self-cross product of all calls to a function that returns one of its parameters
// (only to later filter that flow out using `TypeTracker::append`).
nodeTo =
DataFlowPrivate::LocalFlow::getParameterDefNode(nodeFrom
.(DataFlowPublic::ParameterNode)
.getParameter())
DataFlowPrivate::LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
}
/**

View File

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