mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge remote-tracking branch 'upstream/main' into incomplete-hostname
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The regular expression parser now groups sequences of normal characters. This reduces the number of instances of `RegExpNormalChar`.
|
||||
4
ruby/ql/lib/change-notes/2022-02-28-orm-write-access.md
Normal file
4
ruby/ql/lib/change-notes/2022-02-28-orm-write-access.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added `OrmWriteAccess` concept to model data written to a database using an object-relational mapping (ORM) library.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.0.10
|
||||
lastReleaseVersion: 0.0.11
|
||||
|
||||
129
ruby/ql/lib/codeql/NumberUtils.qll
Normal file
129
ruby/ql/lib/codeql/NumberUtils.qll
Normal 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) }
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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`.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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`.
|
||||
*/
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user