mirror of
https://github.com/github/codeql.git
synced 2026-05-13 02:39:26 +02:00
merge in main
This commit is contained in:
BIN
ruby/Cargo.lock
generated
BIN
ruby/Cargo.lock
generated
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
description: Update ERB parser
|
||||
compatibility: partial
|
||||
|
||||
erb_comment_directive_def.rel: reorder erb_comment_directive_child.rel (int id, int child) id child
|
||||
erb_comment_directive_child.rel: delete
|
||||
|
||||
erb_directive_def.rel: reorder erb_directive_child.rel (int id, int child) id child
|
||||
erb_directive_child.rel: delete
|
||||
|
||||
erb_graphql_directive_def.rel: reorder erb_graphql_directive_child.rel (int id, int child) id child
|
||||
erb_graphql_directive_child.rel: delete
|
||||
|
||||
erb_output_directive_def.rel: reorder erb_output_directive_child.rel (int id, int child) id child
|
||||
erb_output_directive_child.rel: delete
|
||||
@@ -10,11 +10,11 @@ edition = "2018"
|
||||
flate2 = "1.0"
|
||||
node-types = { path = "../node-types" }
|
||||
tree-sitter = "0.19"
|
||||
tree-sitter-embedded-template = "0.19"
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "1a3936a3545c0bd9344a0bf983fafc7e17443e39" }
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "6334d6ab3d04a5672da695d3b155ca3301511f8d" }
|
||||
clap = "3.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
rayon = "1.5.0"
|
||||
num_cpus = "1.13.0"
|
||||
regex = "1.4.3"
|
||||
regex = "1.5.5"
|
||||
|
||||
@@ -11,5 +11,5 @@ clap = "3.0"
|
||||
node-types = { path = "../node-types" }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
tree-sitter-embedded-template = "0.19"
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "1a3936a3545c0bd9344a0bf983fafc7e17443e39" }
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "6334d6ab3d04a5672da695d3b155ca3301511f8d" }
|
||||
|
||||
@@ -9,5 +9,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
n instanceof BlockArgumentNode
|
||||
or
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof HashSplatArgumentsNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
## 0.2.3
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added data-flow support for [hashes](https://docs.ruby-lang.org/en/3.1/Hash.html).
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for data flow through instance variables has been added.
|
||||
* Support of the safe navigation operator (`&.`) has been added; there is a new predicate `MethodCall.isSafeNavigation()`.
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The Tree-sitter Ruby grammar has been updated; this fixes several issues where Ruby code was parsed incorrectly.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: fix
|
||||
---
|
||||
The Tree-sitter Ruby grammar has been updated; this fixes several issues where Ruby code was parsed incorrectly.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
|
||||
5
ruby/ql/lib/change-notes/released/0.2.1.md
Normal file
5
ruby/ql/lib/change-notes/released/0.2.1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.2.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The Tree-sitter Ruby grammar has been updated; this fixes several issues where Ruby code was parsed incorrectly.
|
||||
10
ruby/ql/lib/change-notes/released/0.2.2.md
Normal file
10
ruby/ql/lib/change-notes/released/0.2.2.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## 0.2.2
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added data-flow support for [hashes](https://docs.ruby-lang.org/en/3.1/Hash.html).
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for data flow through instance variables has been added.
|
||||
* Support of the safe navigation operator (`&.`) has been added; there is a new predicate `MethodCall.isSafeNavigation()`.
|
||||
5
ruby/ql/lib/change-notes/released/0.2.3.md
Normal file
5
ruby/ql/lib/change-notes/released/0.2.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.2.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- Calls to `Zip::File.open` and `Zip::File.new` have been added as `FileSystemAccess` sinks. As a result queries like `rb/path-injection` now flag up cases where users may access arbitrary archive files.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.0
|
||||
lastReleaseVersion: 0.2.3
|
||||
|
||||
@@ -576,7 +576,7 @@ module API {
|
||||
use(pred, a) and
|
||||
use(succ, b) and
|
||||
resolveConstant(b.asExpr().getExpr()) = resolveConstantWriteAccess(c) and
|
||||
c.getSuperclassExpr() = a.asExpr().getExpr() and
|
||||
pragma[only_bind_into](c).getSuperclassExpr() = a.asExpr().getExpr() and
|
||||
lbl = Label::subclass()
|
||||
)
|
||||
or
|
||||
@@ -780,6 +780,24 @@ module API {
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = Label::blockParameter()
|
||||
or
|
||||
pos.isAny() and
|
||||
(
|
||||
result = Label::parameter(_)
|
||||
or
|
||||
result = Label::keywordParameter(_)
|
||||
or
|
||||
result = Label::blockParameter()
|
||||
// NOTE: `self` should NOT be included, as described in the QLDoc for `isAny()`
|
||||
)
|
||||
or
|
||||
pos.isAnyNamed() and
|
||||
result = Label::keywordParameter(_)
|
||||
//
|
||||
// Note: there is currently no API graph label for `self`.
|
||||
// It was omitted since in practice it means going back to where you came from.
|
||||
// For example, `base.getMethod("foo").getSelf()` would just be `base`.
|
||||
// However, it's possible we'll need it later, for identifying `self` parameters or post-update nodes.
|
||||
}
|
||||
|
||||
/** Gets the API graph label corresponding to the given parameter position. */
|
||||
@@ -796,6 +814,24 @@ module API {
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = Label::blockParameter()
|
||||
or
|
||||
pos.isAny() and
|
||||
(
|
||||
result = Label::parameter(_)
|
||||
or
|
||||
result = Label::keywordParameter(_)
|
||||
or
|
||||
result = Label::blockParameter()
|
||||
// NOTE: `self` should NOT be included, as described in the QLDoc for `isAny()`
|
||||
)
|
||||
or
|
||||
pos.isAnyNamed() and
|
||||
result = Label::keywordParameter(_)
|
||||
//
|
||||
// Note: there is currently no API graph label for `self`.
|
||||
// It was omitted since in practice it means going back to where you came from.
|
||||
// For example, `base.getMethod("foo").getSelf()` would just be `base`.
|
||||
// However, it's possible we'll need it later, for identifying `self` parameters or post-update nodes.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,7 +826,19 @@ module Logging {
|
||||
* to improve our libraries in the future to more precisely capture this aspect.
|
||||
*/
|
||||
module Cryptography {
|
||||
import security.CryptoAlgorithms
|
||||
// Since we still rely on `isWeak` predicate on `CryptographicOperation` in Ruby, we
|
||||
// modify that part of the shared concept... which means we have to explicitly
|
||||
// re-export everything else.
|
||||
// Using SC shorthand for "Shared Cryptography"
|
||||
import codeql.ruby.internal.ConceptsShared::Cryptography as SC
|
||||
|
||||
class CryptographicAlgorithm = SC::CryptographicAlgorithm;
|
||||
|
||||
class EncryptionAlgorithm = SC::EncryptionAlgorithm;
|
||||
|
||||
class HashingAlgorithm = SC::HashingAlgorithm;
|
||||
|
||||
class PasswordHashingAlgorithm = SC::PasswordHashingAlgorithm;
|
||||
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
@@ -835,15 +847,9 @@ module Cryptography {
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CryptographicOperation::Range` instead.
|
||||
*/
|
||||
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
|
||||
/** Holds if this encryption operation is known to be weak. */
|
||||
predicate isWeak() { super.isWeak() }
|
||||
class CryptographicOperation extends SC::CryptographicOperation instanceof CryptographicOperation::Range {
|
||||
/** DEPRECATED: Use `getAlgorithm().isWeak() or getBlockMode().isWeak()` instead */
|
||||
deprecated predicate isWeak() { super.isWeak() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling new applications of a cryptographic algorithms. */
|
||||
@@ -855,15 +861,11 @@ module Cryptography {
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CryptographicOperation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
abstract CryptographicAlgorithm getAlgorithm();
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
|
||||
/** Holds if this encryption operation is known to be weak. */
|
||||
abstract predicate isWeak();
|
||||
abstract class Range extends SC::CryptographicOperation::Range {
|
||||
/** DEPRECATED: Use `getAlgorithm().isWeak() or getBlockMode().isWeak()` instead */
|
||||
deprecated predicate isWeak() { this.getAlgorithm().isWeak() or this.getBlockMode().isWeak() }
|
||||
}
|
||||
}
|
||||
|
||||
class BlockMode = SC::BlockMode;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.ActiveSupport
|
||||
private import codeql.ruby.frameworks.Archive
|
||||
private import codeql.ruby.frameworks.GraphQL
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.Stdlib
|
||||
@@ -15,3 +16,4 @@ private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.frameworks.HttpClients
|
||||
private import codeql.ruby.frameworks.XmlParsing
|
||||
private import codeql.ruby.frameworks.ActionDispatch
|
||||
private import codeql.ruby.frameworks.PosixSpawn
|
||||
|
||||
@@ -105,6 +105,14 @@ class MethodCall extends Call instanceof MethodCallImpl {
|
||||
*/
|
||||
final Block getBlock() { result = super.getBlockImpl() }
|
||||
|
||||
/**
|
||||
* Holds if the safe nagivation operator (`&.`) is used in this call.
|
||||
* ```rb
|
||||
* foo&.empty?
|
||||
* ```
|
||||
*/
|
||||
final predicate isSafeNavigation() { super.isSafeNavigationImpl() }
|
||||
|
||||
override string toString() { result = "call to " + this.getMethodName() }
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
@@ -137,6 +145,21 @@ class SetterMethodCall extends MethodCall, TMethodCallSynth {
|
||||
SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SetterMethodCall" }
|
||||
|
||||
/**
|
||||
* Gets the name of the method being called without the trailing `=`. For example, in the following
|
||||
* two statements the target name is `value`:
|
||||
* ```rb
|
||||
* foo.value=(1)
|
||||
* foo.value = 1
|
||||
* ```
|
||||
*/
|
||||
final string getTargetName() {
|
||||
exists(string methodName |
|
||||
methodName = this.getMethodName() and
|
||||
result = methodName.prefix(methodName.length() - 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,10 +111,10 @@ class IfExpr extends ConditionalExpr, TIfExpr {
|
||||
}
|
||||
}
|
||||
|
||||
private class If extends IfExpr, TIf {
|
||||
private class IfReal extends IfExpr, TIfReal {
|
||||
private Ruby::If g;
|
||||
|
||||
If() { this = TIf(g) }
|
||||
IfReal() { this = TIfReal(g) }
|
||||
|
||||
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
|
||||
|
||||
@@ -125,6 +125,18 @@ private class If extends IfExpr, TIf {
|
||||
final override string toString() { result = "if ..." }
|
||||
}
|
||||
|
||||
private class IfSynth extends IfExpr, TIfSynth {
|
||||
IfSynth() { this = TIfSynth(_, _) }
|
||||
|
||||
final override Expr getCondition() { synthChild(this, 0, result) }
|
||||
|
||||
final override Stmt getThen() { synthChild(this, 1, result) }
|
||||
|
||||
final override Stmt getElse() { synthChild(this, 2, result) }
|
||||
|
||||
final override string toString() { result = "if ..." }
|
||||
}
|
||||
|
||||
private class Elsif extends IfExpr, TElsif {
|
||||
private Ruby::Elsif g;
|
||||
|
||||
|
||||
@@ -198,7 +198,11 @@ class ErbCommentDirective extends ErbDirective {
|
||||
|
||||
override ErbComment getToken() { toGenerated(result) = g.getChild() }
|
||||
|
||||
final override string toString() { result = "<%#" + this.getToken().toString() + "%>" }
|
||||
final override string toString() {
|
||||
result = "<%#" + this.getToken().toString() + "%>"
|
||||
or
|
||||
not exists(this.getToken()) and result = "<%#%>"
|
||||
}
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ErbCommentDirective" }
|
||||
}
|
||||
@@ -223,7 +227,11 @@ class ErbGraphqlDirective extends ErbDirective {
|
||||
|
||||
override ErbCode getToken() { toGenerated(result) = g.getChild() }
|
||||
|
||||
final override string toString() { result = "<%graphql" + this.getToken().toString() + "%>" }
|
||||
final override string toString() {
|
||||
result = "<%graphql" + this.getToken().toString() + "%>"
|
||||
or
|
||||
not exists(this.getToken()) and result = "<%graphql%>"
|
||||
}
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ErbGraphqlDirective" }
|
||||
}
|
||||
@@ -248,7 +256,11 @@ class ErbOutputDirective extends ErbDirective {
|
||||
|
||||
override ErbCode getToken() { toGenerated(result) = g.getChild() }
|
||||
|
||||
final override string toString() { result = "<%=" + this.getToken().toString() + "%>" }
|
||||
final override string toString() {
|
||||
result = "<%=" + this.getToken().toString() + "%>"
|
||||
or
|
||||
not exists(this.getToken()) and result = "<%=%>"
|
||||
}
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ErbOutputDirective" }
|
||||
}
|
||||
@@ -266,7 +278,11 @@ class ErbExecutionDirective extends ErbDirective {
|
||||
|
||||
ErbExecutionDirective() { this = TDirective(g) }
|
||||
|
||||
final override string toString() { result = "<%" + this.getToken().toString() + "%>" }
|
||||
final override string toString() {
|
||||
result = "<%" + this.getToken().toString() + "%>"
|
||||
or
|
||||
not exists(this.getToken()) and result = "<%-%>"
|
||||
}
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ErbExecutionDirective" }
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class NamedParameter extends Parameter, TNamedParameter {
|
||||
final VariableAccess getDefiningAccess() {
|
||||
result = this.getVariable().getDefiningAccess()
|
||||
or
|
||||
result = this.(SimpleParameterSynthImpl).getDefininingAccess()
|
||||
result = this.(SimpleParameterSynthImpl).getDefiningAccess()
|
||||
}
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
|
||||
@@ -116,7 +116,7 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
|
||||
predicate isImplicitWrite() {
|
||||
implicitWriteAccess(toGenerated(this))
|
||||
or
|
||||
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
|
||||
this = any(SimpleParameterSynthImpl p).getDefiningAccess()
|
||||
or
|
||||
this = any(HashPattern p).getValue(_)
|
||||
or
|
||||
@@ -181,6 +181,17 @@ class GlobalVariableReadAccess extends GlobalVariableAccess, VariableReadAccess
|
||||
/** An access to an instance variable. */
|
||||
class InstanceVariableAccess extends VariableAccess instanceof InstanceVariableAccessImpl {
|
||||
final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
|
||||
|
||||
/**
|
||||
* Gets the synthetic receiver (`self`) of this instance variable access.
|
||||
*/
|
||||
final SelfVariableAccess getReceiver() { synthChild(this, 0, result) }
|
||||
|
||||
final override AstNode getAChild(string pred) {
|
||||
result = VariableAccess.super.getAChild(pred)
|
||||
or
|
||||
pred = "getReceiver" and result = this.getReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
/** An access to an instance variable where the value is updated. */
|
||||
|
||||
@@ -162,7 +162,8 @@ private module Cached {
|
||||
} or
|
||||
THereDoc(Ruby::HeredocBeginning g) or
|
||||
TIdentifierMethodCall(Ruby::Identifier g) { isIdentifierMethodCall(g) } or
|
||||
TIf(Ruby::If g) or
|
||||
TIfReal(Ruby::If g) or
|
||||
TIfSynth(AST::AstNode parent, int i) { mkSynthChild(IfKind(), parent, i) } or
|
||||
TIfModifierExpr(Ruby::IfModifier g) or
|
||||
TInClause(Ruby::InClause g) or
|
||||
TInstanceVariableAccessReal(Ruby::InstanceVariable g, AST::InstanceVariable v) {
|
||||
@@ -214,7 +215,8 @@ private module Cached {
|
||||
TMulExprSynth(AST::AstNode parent, int i) { mkSynthChild(MulExprKind(), parent, i) } or
|
||||
TNEExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangequal } or
|
||||
TNextStmt(Ruby::Next g) or
|
||||
TNilLiteral(Ruby::Nil g) or
|
||||
TNilLiteralReal(Ruby::Nil g) or
|
||||
TNilLiteralSynth(AST::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
|
||||
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
|
||||
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
|
||||
TOptionalParameter(Ruby::OptionalParameter g) or
|
||||
@@ -347,35 +349,36 @@ private module Cached {
|
||||
TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or TForwardParameter or
|
||||
TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
|
||||
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
|
||||
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or TIf or
|
||||
TIfModifierExpr or TInClause or TInstanceVariableAccessReal or TIntegerLiteralReal or
|
||||
TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or TLambda or
|
||||
TLeftAssignmentList or TLine or TLocalVariableAccessReal or TLogicalAndExprReal or
|
||||
TLogicalOrExprReal or TMethod or TModuleDeclaration or TModuloExprReal or TMulExprReal or
|
||||
TNEExpr or TNextStmt or TNilLiteral or TNoRegExpMatchExpr or TNotExpr or
|
||||
TOptionalParameter or TPair or TParenthesizedExpr or TParenthesizedPattern or
|
||||
TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or
|
||||
TRegExpMatchExpr or TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or
|
||||
TRegularSuperCall or TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
|
||||
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
|
||||
TSimpleSymbolLiteral or TSingletonClass or TSingletonMethod or TSpaceshipExpr or
|
||||
TSplatExprReal or TSplatParameter or TStringArrayLiteral or TStringConcatenation or
|
||||
TStringEscapeSequenceComponent or TStringInterpolationComponent or TStringTextComponent or
|
||||
TSubExprReal or TSubshellLiteral or TSymbolArrayLiteral or TTernaryIfExpr or TThen or
|
||||
TTokenConstantAccess or TTokenMethodName or TTokenSuperCall or TToplevel or TTrueLiteral or
|
||||
TUnaryMinusExpr or TUnaryPlusExpr or TUndefStmt or TUnlessExpr or TUnlessModifierExpr or
|
||||
TUntilExpr or TUntilModifierExpr or TReferencePattern or TWhenClause or TWhileExpr or
|
||||
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
|
||||
TIfReal or TIfModifierExpr or TInClause or TInstanceVariableAccessReal or
|
||||
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
|
||||
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
|
||||
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TModuleDeclaration or
|
||||
TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or TNilLiteralReal or
|
||||
TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or TParenthesizedExpr or
|
||||
TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or
|
||||
TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or TRegularArrayLiteral or
|
||||
TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or TRescueClause or
|
||||
TRescueModifierExpr or TRetryStmt or TReturnStmt or TScopeResolutionConstantAccess or
|
||||
TSelfReal or TSimpleParameterReal or TSimpleSymbolLiteral or TSingletonClass or
|
||||
TSingletonMethod or TSpaceshipExpr or TSplatExprReal or TSplatParameter or
|
||||
TStringArrayLiteral or TStringConcatenation or TStringEscapeSequenceComponent or
|
||||
TStringInterpolationComponent or TStringTextComponent or TSubExprReal or TSubshellLiteral or
|
||||
TSymbolArrayLiteral or TTernaryIfExpr or TThen or TTokenConstantAccess or
|
||||
TTokenMethodName or TTokenSuperCall or TToplevel or TTrueLiteral or TUnaryMinusExpr or
|
||||
TUnaryPlusExpr or TUndefStmt or TUnlessExpr or TUnlessModifierExpr or TUntilExpr or
|
||||
TUntilModifierExpr or TReferencePattern or TWhenClause or TWhileExpr or
|
||||
TWhileModifierExpr or TYieldCall;
|
||||
|
||||
class TAstNodeSynth =
|
||||
TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
|
||||
TBitwiseXorExprSynth or TBraceBlockSynth or TClassVariableAccessSynth or
|
||||
TConstantReadAccessSynth or TDivExprSynth or TExponentExprSynth or
|
||||
TGlobalVariableAccessSynth or TInstanceVariableAccessSynth or TIntegerLiteralSynth or
|
||||
TLShiftExprSynth or TLocalVariableAccessSynth or TLogicalAndExprSynth or
|
||||
TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or TMulExprSynth or
|
||||
TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or TSimpleParameterSynth or
|
||||
TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
|
||||
TGlobalVariableAccessSynth or TIfSynth or TInstanceVariableAccessSynth or
|
||||
TIntegerLiteralSynth or TLShiftExprSynth or TLocalVariableAccessSynth or
|
||||
TLogicalAndExprSynth or TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or
|
||||
TMulExprSynth or TNilLiteralSynth or TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or
|
||||
TSimpleParameterSynth or TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
|
||||
|
||||
/**
|
||||
* Gets the underlying TreeSitter entity for a given AST node. This does not
|
||||
@@ -457,7 +460,7 @@ private module Cached {
|
||||
n = THereDoc(result) or
|
||||
n = TIdentifierMethodCall(result) or
|
||||
n = TIfModifierExpr(result) or
|
||||
n = TIf(result) or
|
||||
n = TIfReal(result) or
|
||||
n = TInClause(result) or
|
||||
n = TInstanceVariableAccessReal(result, _) or
|
||||
n = TIntegerLiteralReal(result) or
|
||||
@@ -477,7 +480,7 @@ private module Cached {
|
||||
n = TMulExprReal(result) or
|
||||
n = TNEExpr(result) or
|
||||
n = TNextStmt(result) or
|
||||
n = TNilLiteral(result) or
|
||||
n = TNilLiteralReal(result) or
|
||||
n = TNoRegExpMatchExpr(result) or
|
||||
n = TNotExpr(result) or
|
||||
n = TOptionalParameter(result) or
|
||||
@@ -568,6 +571,8 @@ private module Cached {
|
||||
or
|
||||
result = TGlobalVariableAccessSynth(parent, i, _)
|
||||
or
|
||||
result = TIfSynth(parent, i)
|
||||
or
|
||||
result = TInstanceVariableAccessSynth(parent, i, _)
|
||||
or
|
||||
result = TIntegerLiteralSynth(parent, i, _)
|
||||
@@ -586,6 +591,8 @@ private module Cached {
|
||||
or
|
||||
result = TMulExprSynth(parent, i)
|
||||
or
|
||||
result = TNilLiteralSynth(parent, i)
|
||||
or
|
||||
result = TRangeLiteralSynth(parent, i, _)
|
||||
or
|
||||
result = TRShiftExprSynth(parent, i)
|
||||
@@ -672,6 +679,8 @@ class TControlExpr = TConditionalExpr or TCaseExpr or TCaseMatch or TLoop;
|
||||
class TConditionalExpr =
|
||||
TIfExpr or TUnlessExpr or TIfModifierExpr or TUnlessModifierExpr or TTernaryIfExpr;
|
||||
|
||||
class TIf = TIfReal or TIfSynth;
|
||||
|
||||
class TIfExpr = TIf or TElsif;
|
||||
|
||||
class TConditionalLoop = TWhileExpr or TUntilExpr or TWhileModifierExpr or TUntilModifierExpr;
|
||||
@@ -695,6 +704,8 @@ class TStmtSequence =
|
||||
|
||||
class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod;
|
||||
|
||||
class TNilLiteral = TNilLiteralReal or TNilLiteralSynth;
|
||||
|
||||
class TLiteral =
|
||||
TEncoding or TFile or TLine or TNumericLiteral or TNilLiteral or TBooleanLiteral or
|
||||
TStringlikeLiteral or TCharacterLiteral or TArrayLiteral or THashLiteral or TRangeLiteral or
|
||||
|
||||
@@ -28,6 +28,8 @@ abstract class MethodCallImpl extends CallImpl, TMethodCall {
|
||||
abstract string getMethodNameImpl();
|
||||
|
||||
abstract Block getBlockImpl();
|
||||
|
||||
predicate isSafeNavigationImpl() { none() }
|
||||
}
|
||||
|
||||
class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
|
||||
@@ -89,6 +91,10 @@ class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
|
||||
final override int getNumberOfArgumentsImpl() { result = count(g.getArguments().getChild(_)) }
|
||||
|
||||
final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
|
||||
|
||||
final override predicate isSafeNavigationImpl() {
|
||||
g.getOperator().(Ruby::Token).getValue() = "&."
|
||||
}
|
||||
}
|
||||
|
||||
class ElementReferenceImpl extends MethodCallImpl, TElementReference {
|
||||
|
||||
@@ -111,12 +111,18 @@ class ComplexLiteralImpl extends Expr, TComplexLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
class NilLiteralImpl extends Expr, TNilLiteral {
|
||||
abstract class NilLiteralImpl extends Expr, TNilLiteral {
|
||||
final override string toString() { result = "nil" }
|
||||
}
|
||||
|
||||
class NilLiteralReal extends NilLiteralImpl, TNilLiteralReal {
|
||||
private Ruby::Nil g;
|
||||
|
||||
NilLiteralImpl() { this = TNilLiteral(g) }
|
||||
NilLiteralReal() { this = TNilLiteralReal(g) }
|
||||
}
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
class NilLiteralSynth extends NilLiteralImpl, TNilLiteralSynth {
|
||||
NilLiteralSynth() { this = TNilLiteralSynth(_, _) }
|
||||
}
|
||||
|
||||
abstract class BooleanLiteralImpl extends Expr, TBooleanLiteral {
|
||||
|
||||
@@ -38,7 +38,7 @@ class SimpleParameterRealImpl extends SimpleParameterImpl, TSimpleParameterReal
|
||||
class SimpleParameterSynthImpl extends SimpleParameterImpl, TSimpleParameterSynth {
|
||||
SimpleParameterSynthImpl() { this = TSimpleParameterSynth(_, _) }
|
||||
|
||||
LocalVariableAccessSynth getDefininingAccess() { synthChild(this, 0, result) }
|
||||
LocalVariableAccessSynth getDefiningAccess() { synthChild(this, 0, result) }
|
||||
|
||||
override LocalVariable getVariableImpl() { result = TLocalVariableSynth(this, _) }
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ newtype SynthKind =
|
||||
DivExprKind() or
|
||||
ExponentExprKind() or
|
||||
GlobalVariableAccessKind(GlobalVariable v) or
|
||||
IfKind() or
|
||||
InstanceVariableAccessKind(InstanceVariable v) or
|
||||
IntegerLiteralKind(int i) { i in [-1000 .. 1000] } or
|
||||
LShiftExprKind() or
|
||||
@@ -33,6 +34,7 @@ newtype SynthKind =
|
||||
} or
|
||||
ModuloExprKind() or
|
||||
MulExprKind() or
|
||||
NilLiteralKind() or
|
||||
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
|
||||
RShiftExprKind() or
|
||||
SimpleParameterKind() or
|
||||
@@ -208,6 +210,38 @@ private module ImplicitSelfSynthesis {
|
||||
regularMethodCallSelfSynthesis(parent, i, child)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private AstNode instanceVarAccessSynthParentStar(InstanceVariableAccess var) {
|
||||
result = var
|
||||
or
|
||||
instanceVarAccessSynthParentStar(var) = getSynthChild(result, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `SelfKind` for instance variable access `var`. This is based on the
|
||||
* "owner" of `var`; for real nodes this is the node itself, for synthetic nodes
|
||||
* this is the closest parent that is a real node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private SelfKind getSelfKind(InstanceVariableAccess var) {
|
||||
exists(Ruby::AstNode owner |
|
||||
owner = toGenerated(instanceVarAccessSynthParentStar(var)) and
|
||||
result = SelfKind(TSelfVariable(scopeOf(owner).getEnclosingSelfScope()))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate instanceVariableSelfSynthesis(InstanceVariableAccess var, int i, Child child) {
|
||||
child = SynthChild(getSelfKind(var)) and
|
||||
i = 0
|
||||
}
|
||||
|
||||
private class InstanceVariableSelfSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
instanceVariableSelfSynthesis(parent, i, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module SetterDesugar {
|
||||
@@ -1083,3 +1117,123 @@ private module AnonymousBlockParameterSynth {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module SafeNavigationCallDesugar {
|
||||
/**
|
||||
* ```rb
|
||||
* receiver&.method(args) { ... }
|
||||
* ```
|
||||
* desugars to
|
||||
*
|
||||
* ```rb
|
||||
* __synth__0 = receiver
|
||||
* if nil == __synth__0 then nil else __synth__0.method(args) {...} end
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate safeNavigationCallSynthesis(AstNode parent, int i, Child child) {
|
||||
exists(RegularMethodCall call, LocalVariableAccessSynthKind local |
|
||||
call.isSafeNavigationImpl() and
|
||||
local = LocalVariableAccessSynthKind(TLocalVariableSynth(call.getReceiverImpl(), 0))
|
||||
|
|
||||
parent = call and
|
||||
i = -1 and
|
||||
child = SynthChild(StmtSequenceKind())
|
||||
or
|
||||
exists(TStmtSequenceSynth seq | seq = TStmtSequenceSynth(call, -1) |
|
||||
parent = seq and
|
||||
(
|
||||
child = SynthChild(AssignExprKind()) and i = 0
|
||||
or
|
||||
child = SynthChild(IfKind()) and i = 1
|
||||
)
|
||||
or
|
||||
parent = TAssignExprSynth(seq, 0) and
|
||||
(
|
||||
child = SynthChild(local) and
|
||||
i = 0
|
||||
or
|
||||
child = childRef(call.getReceiverImpl()) and i = 1
|
||||
)
|
||||
or
|
||||
exists(TIfSynth ifExpr | ifExpr = TIfSynth(seq, 1) |
|
||||
parent = ifExpr and
|
||||
(
|
||||
child = SynthChild(MethodCallKind("==", false, 2)) and
|
||||
i = 0
|
||||
or
|
||||
child = SynthChild(NilLiteralKind()) and i = 1
|
||||
or
|
||||
child =
|
||||
SynthChild(MethodCallKind(call.getMethodNameImpl(), false,
|
||||
call.getNumberOfArgumentsImpl())) and
|
||||
i = 2
|
||||
)
|
||||
or
|
||||
parent = TMethodCallSynth(ifExpr, 0, _, _, _) and
|
||||
(
|
||||
child = SynthChild(NilLiteralKind()) and i = 0
|
||||
or
|
||||
child = SynthChild(local) and
|
||||
i = 1
|
||||
)
|
||||
or
|
||||
parent = TMethodCallSynth(ifExpr, 2, _, _, _) and
|
||||
(
|
||||
i = 0 and
|
||||
child = SynthChild(local)
|
||||
or
|
||||
child = childRef(call.getArgumentImpl(i - 1))
|
||||
or
|
||||
child = childRef(call.getBlockImpl()) and i = -2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private class SafeNavigationCallSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
safeNavigationCallSynthesis(parent, i, child)
|
||||
}
|
||||
|
||||
final override predicate methodCall(string name, boolean setter, int arity) {
|
||||
exists(RegularMethodCall call |
|
||||
call.isSafeNavigationImpl() and
|
||||
name = call.getMethodNameImpl() and
|
||||
setter = false and
|
||||
arity = call.getNumberOfArgumentsImpl()
|
||||
)
|
||||
or
|
||||
name = "==" and setter = false and arity = 2
|
||||
}
|
||||
|
||||
final override predicate localVariable(AstNode n, int i) {
|
||||
i = 0 and n = any(RegularMethodCall c | c.isSafeNavigationImpl()).getReceiverImpl()
|
||||
}
|
||||
|
||||
override predicate location(AstNode n, Location l) {
|
||||
exists(RegularMethodCall call, StmtSequence seq |
|
||||
call.isSafeNavigationImpl() and seq = call.getDesugared()
|
||||
|
|
||||
n = seq.getStmt(0) and
|
||||
hasLocation(call.getReceiverImpl(), l)
|
||||
or
|
||||
n = seq.getStmt(1) and
|
||||
l = toGenerated(call).(Ruby::Call).getOperator().getLocation()
|
||||
or
|
||||
n = seq.getStmt(1).(IfExpr).getCondition().(MethodCall).getArgument(0) and
|
||||
hasLocation(call.getReceiverImpl(), l)
|
||||
or
|
||||
n = seq.getStmt(1).(IfExpr).getThen() and
|
||||
hasLocation(call.getReceiverImpl(), l)
|
||||
or
|
||||
n = seq.getStmt(1).(IfExpr).getElse() and
|
||||
hasLocation(call, l)
|
||||
or
|
||||
n = seq.getStmt(1).(IfExpr).getElse().(MethodCall).getReceiver() and
|
||||
hasLocation(call.getReceiverImpl(), l)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1925,10 +1925,10 @@ module Erb {
|
||||
final override string getAPrimaryQlClass() { result = "CommentDirective" }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
final Comment getChild() { erb_comment_directive_def(this, result) }
|
||||
final Comment getChild() { erb_comment_directive_child(this, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() { erb_comment_directive_def(this, result) }
|
||||
final override AstNode getAFieldOrChild() { erb_comment_directive_child(this, result) }
|
||||
}
|
||||
|
||||
/** A class representing `content` tokens. */
|
||||
@@ -1943,10 +1943,10 @@ module Erb {
|
||||
final override string getAPrimaryQlClass() { result = "Directive" }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
final Code getChild() { erb_directive_def(this, result) }
|
||||
final Code getChild() { erb_directive_child(this, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() { erb_directive_def(this, result) }
|
||||
final override AstNode getAFieldOrChild() { erb_directive_child(this, result) }
|
||||
}
|
||||
|
||||
/** A class representing `graphql_directive` nodes. */
|
||||
@@ -1955,10 +1955,10 @@ module Erb {
|
||||
final override string getAPrimaryQlClass() { result = "GraphqlDirective" }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
final Code getChild() { erb_graphql_directive_def(this, result) }
|
||||
final Code getChild() { erb_graphql_directive_child(this, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() { erb_graphql_directive_def(this, result) }
|
||||
final override AstNode getAFieldOrChild() { erb_graphql_directive_child(this, result) }
|
||||
}
|
||||
|
||||
/** A class representing `output_directive` nodes. */
|
||||
@@ -1967,10 +1967,10 @@ module Erb {
|
||||
final override string getAPrimaryQlClass() { result = "OutputDirective" }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
final Code getChild() { erb_output_directive_def(this, result) }
|
||||
final Code getChild() { erb_output_directive_child(this, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() { erb_output_directive_def(this, result) }
|
||||
final override AstNode getAFieldOrChild() { erb_output_directive_child(this, result) }
|
||||
}
|
||||
|
||||
/** A class representing `template` nodes. */
|
||||
|
||||
@@ -11,6 +11,8 @@ private import internal.Splitting
|
||||
|
||||
/** An entry node for a given scope. */
|
||||
class EntryNode extends CfgNode, TEntryNode {
|
||||
override string getAPrimaryQlClass() { result = "EntryNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
|
||||
EntryNode() { this = TEntryNode(scope) }
|
||||
@@ -24,6 +26,8 @@ class EntryNode extends CfgNode, TEntryNode {
|
||||
|
||||
/** An exit node for a given scope, annotated with the type of exit. */
|
||||
class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
|
||||
override string getAPrimaryQlClass() { result = "AnnotatedExitNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
private boolean normal;
|
||||
|
||||
@@ -49,6 +53,8 @@ class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
|
||||
|
||||
/** An exit node for a given scope. */
|
||||
class ExitNode extends CfgNode, TExitNode {
|
||||
override string getAPrimaryQlClass() { result = "ExitNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
|
||||
ExitNode() { this = TExitNode(scope) }
|
||||
@@ -66,6 +72,9 @@ class ExitNode extends CfgNode, TExitNode {
|
||||
* splits for the AST node.
|
||||
*/
|
||||
class AstCfgNode extends CfgNode, TElementNode {
|
||||
/** Gets the name of the primary QL class for this node. */
|
||||
override string getAPrimaryQlClass() { result = "AstCfgNode" }
|
||||
|
||||
private Splits splits;
|
||||
AstNode e;
|
||||
|
||||
@@ -95,6 +104,8 @@ class AstCfgNode extends CfgNode, TElementNode {
|
||||
|
||||
/** A control-flow node that wraps an AST expression. */
|
||||
class ExprCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ExprCfgNode" }
|
||||
|
||||
override Expr e;
|
||||
|
||||
ExprCfgNode() { e = this.getNode() }
|
||||
@@ -115,6 +126,8 @@ class ExprCfgNode extends AstCfgNode {
|
||||
|
||||
/** A control-flow node that wraps a return-like statement. */
|
||||
class ReturningCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ReturningCfgNode" }
|
||||
|
||||
ReturningStmt s;
|
||||
|
||||
ReturningCfgNode() { s = this.getNode() }
|
||||
@@ -128,6 +141,8 @@ class ReturningCfgNode extends AstCfgNode {
|
||||
|
||||
/** A control-flow node that wraps a `StringComponent` AST expression. */
|
||||
class StringComponentCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringComponentCfgNode" }
|
||||
|
||||
StringComponentCfgNode() { this.getNode() instanceof StringComponent }
|
||||
|
||||
/** Gets the constant value of this string component. */
|
||||
@@ -136,6 +151,8 @@ class StringComponentCfgNode extends AstCfgNode {
|
||||
|
||||
/** A control-flow node that wraps a `RegExpComponent` AST expression. */
|
||||
class RegExpComponentCfgNode extends StringComponentCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "RegExpComponentCfgNode" }
|
||||
|
||||
RegExpComponentCfgNode() { e instanceof RegExpComponent }
|
||||
}
|
||||
|
||||
@@ -219,6 +236,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `ArrayLiteral` AST expression. */
|
||||
class LiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "LiteralCfgNode" }
|
||||
|
||||
override LiteralChildMapping e;
|
||||
|
||||
override Literal getExpr() { result = super.getExpr() }
|
||||
@@ -230,6 +249,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `AssignExpr` AST expression. */
|
||||
class AssignExprCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "AssignExprCfgNode" }
|
||||
|
||||
override AssignExprChildMapping e;
|
||||
|
||||
final override AssignExpr getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -247,6 +268,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `Operation` AST expression. */
|
||||
class OperationCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "OperationCfgNode" }
|
||||
|
||||
override OperationExprChildMapping e;
|
||||
|
||||
override Operation getExpr() { result = super.getExpr() }
|
||||
@@ -260,6 +283,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `UnaryOperation` AST expression. */
|
||||
class UnaryOperationCfgNode extends OperationCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "UnaryOperationCfgNode" }
|
||||
|
||||
private UnaryOperation uo;
|
||||
|
||||
UnaryOperationCfgNode() { e = uo }
|
||||
@@ -272,6 +297,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `BinaryOperation` AST expression. */
|
||||
class BinaryOperationCfgNode extends OperationCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "BinaryOperationCfgNode" }
|
||||
|
||||
private BinaryOperation bo;
|
||||
|
||||
BinaryOperationCfgNode() { e = bo }
|
||||
@@ -291,6 +318,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `BlockArgument` AST expression. */
|
||||
class BlockArgumentCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "BlockArgumentCfgNode" }
|
||||
|
||||
override BlockArgumentChildMapping e;
|
||||
|
||||
final override BlockArgument getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -307,6 +336,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `Call` AST expression. */
|
||||
class CallCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "CallCfgNode" }
|
||||
|
||||
override CallExprChildMapping e;
|
||||
|
||||
override Call getExpr() { result = super.getExpr() }
|
||||
@@ -338,6 +369,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `MethodCall` AST expression. */
|
||||
class MethodCallCfgNode extends CallCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "MethodCallCfgNode" }
|
||||
|
||||
MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
|
||||
|
||||
override MethodCall getExpr() { result = super.getExpr() }
|
||||
@@ -349,6 +382,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `CaseExpr` AST expression. */
|
||||
class CaseExprCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "CaseExprCfgNode" }
|
||||
|
||||
override CaseExprChildMapping e;
|
||||
|
||||
final override CaseExpr getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -372,6 +407,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `InClause` AST expression. */
|
||||
class InClauseCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
|
||||
|
||||
override InClauseChildMapping e;
|
||||
|
||||
/** Gets the pattern in this `in`-clause. */
|
||||
@@ -390,6 +427,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `WhenClause` AST expression. */
|
||||
class WhenClauseCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
|
||||
|
||||
override WhenClauseChildMapping e;
|
||||
|
||||
/** Gets the body of this `when`-clause. */
|
||||
@@ -398,6 +437,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `CasePattern`. */
|
||||
class CasePatternCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
|
||||
|
||||
override CasePattern e;
|
||||
}
|
||||
|
||||
@@ -411,6 +452,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `ArrayPattern` node. */
|
||||
class ArrayPatternCfgNode extends CasePatternCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ArrayPatternCfgNode" }
|
||||
|
||||
override ArrayPatternChildMapping e;
|
||||
|
||||
/** Gets the `n`th element of this list pattern's prefix. */
|
||||
@@ -439,6 +482,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `FindPattern` node. */
|
||||
class FindPatternCfgNode extends CasePatternCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "FindPatternCfgNode" }
|
||||
|
||||
override FindPatternChildMapping e;
|
||||
|
||||
/** Gets the `n`th element of this find pattern. */
|
||||
@@ -464,6 +509,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `HashPattern` node. */
|
||||
class HashPatternCfgNode extends CasePatternCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "HashPatternCfgNode" }
|
||||
|
||||
override HashPatternChildMapping e;
|
||||
|
||||
/** Gets the value of the `n`th pair. */
|
||||
@@ -481,6 +528,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `AlternativePattern` node. */
|
||||
class AlternativePatternCfgNode extends CasePatternCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "AlternativePatternCfgNode" }
|
||||
|
||||
override AlternativePatternChildMapping e;
|
||||
|
||||
/** Gets the `n`th alternative. */
|
||||
@@ -497,6 +546,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `AsPattern` node. */
|
||||
class AsPatternCfgNode extends CasePatternCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "AsPatternCfgNode" }
|
||||
|
||||
override AsPatternChildMapping e;
|
||||
|
||||
/** Gets the underlying pattern. */
|
||||
@@ -514,6 +565,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ParenthesizedPattern` node. */
|
||||
class ParenthesizedPatternCfgNode extends CasePatternCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ParenthesizedPatternCfgNode" }
|
||||
|
||||
override ParenthesizedPatternChildMapping e;
|
||||
|
||||
/** Gets the underlying pattern. */
|
||||
@@ -526,6 +579,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ConditionalExpr` AST expression. */
|
||||
class ConditionalExprCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ConditionalExprCfgNode" }
|
||||
|
||||
override ConditionalExprChildMapping e;
|
||||
|
||||
final override ConditionalExpr getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -546,6 +601,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ConditionalExpr` AST expression. */
|
||||
class ConstantAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ConstantAccessCfgNode" }
|
||||
|
||||
override ConstantAccessChildMapping e;
|
||||
|
||||
final override ConstantAccess getExpr() { result = super.getExpr() }
|
||||
@@ -560,6 +617,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `StmtSequence` AST expression. */
|
||||
class StmtSequenceCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StmtSequenceCfgNode" }
|
||||
|
||||
override StmtSequenceChildMapping e;
|
||||
|
||||
final override StmtSequence getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -574,6 +633,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ForExpr` AST expression. */
|
||||
class ForExprCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ForExprCfgNode" }
|
||||
|
||||
override ForExprChildMapping e;
|
||||
|
||||
final override ForExpr getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -584,6 +645,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ParenthesizedExpr` AST expression. */
|
||||
class ParenthesizedExprCfgNode extends StmtSequenceCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ParenthesizedExprCfgNode" }
|
||||
|
||||
ParenthesizedExprCfgNode() { this.getExpr() instanceof ParenthesizedExpr }
|
||||
}
|
||||
|
||||
@@ -593,6 +656,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `Pair` AST expression. */
|
||||
class PairCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "PairCfgNode" }
|
||||
|
||||
override PairChildMapping e;
|
||||
|
||||
final override Pair getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -610,13 +675,35 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `VariableReadAccess` AST expression. */
|
||||
class VariableReadAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "VariableReadAccessCfgNode" }
|
||||
|
||||
override VariableReadAccess e;
|
||||
|
||||
final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
}
|
||||
|
||||
private class InstanceVariableAccessMapping extends ExprChildMapping, InstanceVariableAccess {
|
||||
override predicate relevantChild(AstNode n) { n = this.getReceiver() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `InstanceVariableAccess` AST expression. */
|
||||
class InstanceVariableAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "InstanceVariableAccessCfgNode" }
|
||||
|
||||
override InstanceVariableAccessMapping e;
|
||||
|
||||
override InstanceVariableAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
|
||||
/**
|
||||
* Gets the synthetic receiver (`self`) of this instance variable access.
|
||||
*/
|
||||
final CfgNode getReceiver() { e.hasCfgChild(e.getReceiver(), this, result) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `VariableWriteAccess` AST expression. */
|
||||
class VariableWriteAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "VariableWriteAccessCfgNode" }
|
||||
|
||||
override VariableWriteAccess e;
|
||||
|
||||
final override VariableWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -624,6 +711,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ConstantReadAccess` AST expression. */
|
||||
class ConstantReadAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ConstantReadAccessCfgNode" }
|
||||
|
||||
override ConstantReadAccess e;
|
||||
|
||||
final override ConstantReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
@@ -631,20 +720,39 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ConstantWriteAccess` AST expression. */
|
||||
class ConstantWriteAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ConstantWriteAccessCfgNode" }
|
||||
|
||||
override ConstantWriteAccess e;
|
||||
|
||||
final override ConstantWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `InstanceVariableWriteAccess` AST expression. */
|
||||
class InstanceVariableWriteAccessCfgNode extends ExprCfgNode {
|
||||
override InstanceVariableWriteAccess e;
|
||||
/** A control-flow node that wraps an `InstanceVariableReadAccess` AST expression. */
|
||||
class InstanceVariableReadAccessCfgNode extends InstanceVariableAccessCfgNode {
|
||||
InstanceVariableReadAccessCfgNode() { this.getNode() instanceof InstanceVariableReadAccess }
|
||||
|
||||
final override InstanceVariableWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
override string getAPrimaryQlClass() { result = "InstanceVariableReadAccessCfgNode" }
|
||||
|
||||
final override InstanceVariableReadAccess getExpr() {
|
||||
result = InstanceVariableAccessCfgNode.super.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `InstanceVariableWriteAccess` AST expression. */
|
||||
class InstanceVariableWriteAccessCfgNode extends InstanceVariableAccessCfgNode {
|
||||
InstanceVariableWriteAccessCfgNode() { this.getNode() instanceof InstanceVariableWriteAccess }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "InstanceVariableWriteAccessCfgNode" }
|
||||
|
||||
final override InstanceVariableWriteAccess getExpr() {
|
||||
result = InstanceVariableAccessCfgNode.super.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringInterpolationComponent` AST expression. */
|
||||
class StringInterpolationComponentCfgNode extends StringComponentCfgNode, StmtSequenceCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringInterpolationComponentCfgNode" }
|
||||
|
||||
StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
|
||||
|
||||
final override ConstantValue getConstantValue() {
|
||||
@@ -654,6 +762,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `RegExpInterpolationComponent` AST expression. */
|
||||
class RegExpInterpolationComponentCfgNode extends RegExpComponentCfgNode, StmtSequenceCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "RegExpInterpolationComponentCfgNode" }
|
||||
|
||||
RegExpInterpolationComponentCfgNode() { this.getNode() instanceof RegExpInterpolationComponent }
|
||||
|
||||
final override ConstantValue getConstantValue() {
|
||||
@@ -667,6 +777,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `StringlikeLiteral` AST expression. */
|
||||
class StringlikeLiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringlikeLiteralCfgNode" }
|
||||
|
||||
override StringlikeLiteralChildMapping e;
|
||||
|
||||
override StringlikeLiteral getExpr() { result = super.getExpr() }
|
||||
@@ -680,6 +792,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
||||
class StringLiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
|
||||
override StringLiteral e;
|
||||
|
||||
final override StringLiteral getExpr() { result = super.getExpr() }
|
||||
@@ -687,6 +801,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `RegExpLiteral` AST expression. */
|
||||
class RegExpLiteralCfgNode extends StringlikeLiteralCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "RegExpLiteralCfgNode" }
|
||||
|
||||
RegExpLiteralCfgNode() { e instanceof RegExpLiteral }
|
||||
|
||||
final override RegExpComponentCfgNode getComponent(int n) { result = super.getComponent(n) }
|
||||
@@ -698,6 +814,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `ComparisonOperation` AST expression. */
|
||||
class ComparisonOperationCfgNode extends BinaryOperationCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ComparisonOperationCfgNode" }
|
||||
|
||||
ComparisonOperationCfgNode() { e instanceof ComparisonOperation }
|
||||
|
||||
override ComparisonOperation getExpr() { result = super.getExpr() }
|
||||
@@ -705,6 +823,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps a `RelationalOperation` AST expression. */
|
||||
class RelationalOperationCfgNode extends ComparisonOperationCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "RelationalOperationCfgNode" }
|
||||
|
||||
RelationalOperationCfgNode() { e instanceof RelationalOperation }
|
||||
|
||||
final override RelationalOperation getExpr() { result = super.getExpr() }
|
||||
@@ -712,6 +832,8 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `ElementReference` AST expression. */
|
||||
class ElementReferenceCfgNode extends MethodCallCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ElementReferenceCfgNode" }
|
||||
|
||||
ElementReferenceCfgNode() { e instanceof ElementReference }
|
||||
|
||||
final override ElementReference getExpr() { result = super.getExpr() }
|
||||
@@ -723,6 +845,8 @@ module ExprNodes {
|
||||
* explicit calls.
|
||||
*/
|
||||
class ArrayLiteralCfgNode extends MethodCallCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ArrayLiteralCfgNode" }
|
||||
|
||||
ArrayLiteralCfgNode() {
|
||||
exists(ConstantReadAccess array |
|
||||
array = this.getReceiver().getExpr() and
|
||||
@@ -739,6 +863,8 @@ module ExprNodes {
|
||||
* explicit calls.
|
||||
*/
|
||||
class HashLiteralCfgNode extends MethodCallCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "HashLiteralCfgNode" }
|
||||
|
||||
HashLiteralCfgNode() {
|
||||
exists(ConstantReadAccess array |
|
||||
array = this.getReceiver().getExpr() and
|
||||
|
||||
@@ -9,7 +9,7 @@ private import internal.Splitting
|
||||
private import internal.Completion
|
||||
|
||||
/** An AST node with an associated control-flow graph. */
|
||||
class CfgScope extends Scope instanceof CfgScope::Range_ {
|
||||
class CfgScope extends Scope instanceof CfgScopeImpl {
|
||||
/** Gets the CFG scope that this scope is nested under, if any. */
|
||||
final CfgScope getOuterCfgScope() {
|
||||
exists(AstNode parent |
|
||||
@@ -28,6 +28,9 @@ class CfgScope extends Scope instanceof CfgScope::Range_ {
|
||||
* Only nodes that can be reached from an entry point are included in the CFG.
|
||||
*/
|
||||
class CfgNode extends TCfgNode {
|
||||
/** Gets the name of the primary QL class for this node. */
|
||||
string getAPrimaryQlClass() { none() }
|
||||
|
||||
/** Gets a textual representation of this control flow node. */
|
||||
string toString() { none() }
|
||||
|
||||
|
||||
@@ -41,61 +41,59 @@ private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import Completion
|
||||
import ControlFlowGraphImplShared
|
||||
|
||||
module CfgScope {
|
||||
abstract class Range_ extends AstNode {
|
||||
abstract predicate entry(AstNode first);
|
||||
abstract class CfgScopeImpl extends AstNode {
|
||||
abstract predicate entry(AstNode first);
|
||||
|
||||
abstract predicate exit(AstNode last, Completion c);
|
||||
abstract predicate exit(AstNode last, Completion c);
|
||||
}
|
||||
|
||||
private class ToplevelScope extends CfgScopeImpl, Toplevel {
|
||||
final override predicate entry(AstNode first) { first(this, first) }
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) { last(this, last, c) }
|
||||
}
|
||||
|
||||
private class EndBlockScope extends CfgScopeImpl, EndBlock {
|
||||
final override predicate entry(AstNode first) {
|
||||
first(this.(Trees::EndBlockTree).getBodyChild(0, _), first)
|
||||
}
|
||||
|
||||
private class ToplevelScope extends Range_, Toplevel {
|
||||
final override predicate entry(AstNode first) { first(this, first) }
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c)
|
||||
or
|
||||
last(this.(Trees::EndBlockTree).getBodyChild(_, _), last, c) and
|
||||
not c instanceof NormalCompletion
|
||||
}
|
||||
}
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) { last(this, last, c) }
|
||||
private class BodyStmtCallableScope extends CfgScopeImpl, ASTInternal::TBodyStmt, Callable {
|
||||
final override predicate entry(AstNode first) { this.(Trees::BodyStmtTree).firstInner(first) }
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
this.(Trees::BodyStmtTree).lastInner(last, c)
|
||||
}
|
||||
}
|
||||
|
||||
private class BraceBlockScope extends CfgScopeImpl, BraceBlock {
|
||||
final override predicate entry(AstNode first) {
|
||||
first(this.(Trees::BraceBlockTree).getBodyChild(0, _), first)
|
||||
}
|
||||
|
||||
private class EndBlockScope extends Range_, EndBlock {
|
||||
final override predicate entry(AstNode first) {
|
||||
first(this.(Trees::EndBlockTree).getBodyChild(0, _), first)
|
||||
}
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c)
|
||||
or
|
||||
last(this.(Trees::EndBlockTree).getBodyChild(_, _), last, c) and
|
||||
not c instanceof NormalCompletion
|
||||
}
|
||||
}
|
||||
|
||||
private class BodyStmtCallableScope extends Range_, ASTInternal::TBodyStmt, Callable {
|
||||
final override predicate entry(AstNode first) { this.(Trees::BodyStmtTree).firstInner(first) }
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
this.(Trees::BodyStmtTree).lastInner(last, c)
|
||||
}
|
||||
}
|
||||
|
||||
private class BraceBlockScope extends Range_, BraceBlock {
|
||||
final override predicate entry(AstNode first) {
|
||||
first(this.(Trees::BraceBlockTree).getBodyChild(0, _), first)
|
||||
}
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c)
|
||||
or
|
||||
last(this.(Trees::BraceBlockTree).getBodyChild(_, _), last, c) and
|
||||
not c instanceof NormalCompletion
|
||||
}
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c)
|
||||
or
|
||||
last(this.(Trees::BraceBlockTree).getBodyChild(_, _), last, c) and
|
||||
not c instanceof NormalCompletion
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `first` is first executed when entering `scope`. */
|
||||
pragma[nomagic]
|
||||
predicate succEntry(CfgScope::Range_ scope, AstNode first) { scope.entry(first) }
|
||||
predicate succEntry(CfgScopeImpl scope, AstNode first) { scope.entry(first) }
|
||||
|
||||
/** Holds if `last` with completion `c` can exit `scope`. */
|
||||
pragma[nomagic]
|
||||
predicate succExit(CfgScope::Range_ scope, AstNode last, Completion c) { scope.exit(last, c) }
|
||||
predicate succExit(CfgScopeImpl scope, AstNode last, Completion c) { scope.exit(last, c) }
|
||||
|
||||
/** Defines the CFG by dispatch on the various AST types. */
|
||||
module Trees {
|
||||
@@ -371,7 +369,9 @@ module Trees {
|
||||
CallTree() {
|
||||
// Logical operations are handled separately
|
||||
not this instanceof UnaryLogicalOperation and
|
||||
not this instanceof BinaryLogicalOperation
|
||||
not this instanceof BinaryLogicalOperation and
|
||||
// Calls with the `&.` operator are desugared
|
||||
not this.(MethodCall).isSafeNavigation()
|
||||
}
|
||||
|
||||
override ControlFlowTree getChildElement(int i) { result = this.getArgument(i) }
|
||||
@@ -1004,7 +1004,9 @@ module Trees {
|
||||
final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) }
|
||||
}
|
||||
|
||||
private class InstanceVariableTree extends LeafTree, InstanceVariableAccess { }
|
||||
private class InstanceVariableTree extends StandardPostOrderTree, InstanceVariableAccess {
|
||||
final override ControlFlowTree getChildElement(int i) { result = this.getReceiver() and i = 0 }
|
||||
}
|
||||
|
||||
private class KeywordParameterTree extends DefaultValueParameterTree, KeywordParameter {
|
||||
final override Expr getDefaultValueExpr() { result = this.getDefaultValue() }
|
||||
@@ -1256,7 +1258,7 @@ module Trees {
|
||||
|
||||
private class SimpleParameterTree extends NonDefaultValueParameterTree, SimpleParameter { }
|
||||
|
||||
// Corner case: For duplicated '_' parameters, only the first occurence has a defining
|
||||
// Corner case: For duplicated '_' parameters, only the first occurrence has a defining
|
||||
// access. For subsequent parameters we simply include the parameter itself in the CFG
|
||||
private class SimpleParameterTreeDupUnderscore extends LeafTree, SimpleParameter {
|
||||
SimpleParameterTreeDupUnderscore() { not exists(this.getDefiningAccess()) }
|
||||
@@ -1427,7 +1429,7 @@ module Trees {
|
||||
|
||||
private Scope parent(Scope n) {
|
||||
result = n.getOuterScope() and
|
||||
not n instanceof CfgScope::Range_
|
||||
not n instanceof CfgScopeImpl
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -744,7 +744,7 @@ cached
|
||||
private module Cached {
|
||||
/**
|
||||
* If needed, call this predicate from `ControlFlowGraphImplSpecific.qll` in order to
|
||||
* force a stage-dependency on the `ControlFlowGraphImplShared.qll` stage and therby
|
||||
* force a stage-dependency on the `ControlFlowGraphImplShared.qll` stage and thereby
|
||||
* collapsing the two stages.
|
||||
*/
|
||||
cached
|
||||
|
||||
@@ -35,12 +35,12 @@ predicate getCfgScope = Impl::getCfgScope/1;
|
||||
|
||||
/** Holds if `first` is first executed when entering `scope`. */
|
||||
predicate scopeFirst(CfgScope scope, ControlFlowElement first) {
|
||||
scope.(Impl::CfgScope::Range_).entry(first)
|
||||
scope.(Impl::CfgScopeImpl).entry(first)
|
||||
}
|
||||
|
||||
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
|
||||
predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c) {
|
||||
scope.(Impl::CfgScope::Range_).exit(last, c)
|
||||
scope.(Impl::CfgScopeImpl).exit(last, c)
|
||||
}
|
||||
|
||||
/** The maximum number of splits allowed for a given node. */
|
||||
|
||||
@@ -4,6 +4,23 @@ private import ruby
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.CFG
|
||||
|
||||
private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
|
||||
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
|
||||
c = g and
|
||||
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
|
||||
c.getExpr() instanceof EqExpr and branch = true
|
||||
or
|
||||
c.getExpr() instanceof CaseEqExpr and branch = true
|
||||
or
|
||||
c.getExpr() instanceof NEExpr and branch = false
|
||||
|
|
||||
c.getLeftOperand() = strLitNode and c.getRightOperand() = e
|
||||
or
|
||||
c.getLeftOperand() = e and c.getRightOperand() = strLitNode
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A validation of value by comparing with a constant string value, for example
|
||||
* in:
|
||||
@@ -17,7 +34,28 @@ private import codeql.ruby.CFG
|
||||
* the equality operation guards against `dir` taking arbitrary values when used
|
||||
* in the `order` call.
|
||||
*/
|
||||
class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
class StringConstCompareBarrier extends DataFlow::Node {
|
||||
StringConstCompareBarrier() {
|
||||
this = DataFlow::BarrierGuard<stringConstCompare/3>::getABarrierNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `StringConstCompareBarrier` instead.
|
||||
*
|
||||
* A validation of value by comparing with a constant string value, for example
|
||||
* in:
|
||||
*
|
||||
* ```rb
|
||||
* dir = params[:order]
|
||||
* dir = "DESC" unless dir == "ASC"
|
||||
* User.order("name #{dir}")
|
||||
* ```
|
||||
*
|
||||
* the equality operation guards against `dir` taking arbitrary values when used
|
||||
* in the `order` call.
|
||||
*/
|
||||
deprecated class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
CfgNodes::ExprNodes::ComparisonOperationCfgNode {
|
||||
private CfgNode checkedNode;
|
||||
// The value of the condition that results in the node being validated.
|
||||
@@ -42,6 +80,18 @@ class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
}
|
||||
}
|
||||
|
||||
private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
|
||||
exists(CfgNodes::ExprNodes::MethodCallCfgNode mc, ArrayLiteral aLit |
|
||||
mc = g and
|
||||
mc.getExpr().getMethodName() = "include?" and
|
||||
[mc.getExpr().getReceiver(), mc.getExpr().getReceiver().(ConstantReadAccess).getValue()] = aLit
|
||||
|
|
||||
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
|
||||
mc.getArgument(0) = e
|
||||
) and
|
||||
branch = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A validation of a value by checking for inclusion in an array of string
|
||||
* literal values, for example in:
|
||||
@@ -56,8 +106,29 @@ class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
* the `include?` call guards against `name` taking arbitrary values when used
|
||||
* in the `find_by` call.
|
||||
*/
|
||||
//
|
||||
class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
|
||||
class StringConstArrayInclusionCallBarrier extends DataFlow::Node {
|
||||
StringConstArrayInclusionCallBarrier() {
|
||||
this = DataFlow::BarrierGuard<stringConstArrayInclusionCall/3>::getABarrierNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `StringConstArrayInclusionCallBarrier` instead.
|
||||
*
|
||||
* A validation of a value by checking for inclusion in an array of string
|
||||
* literal values, for example in:
|
||||
*
|
||||
* ```rb
|
||||
* name = params[:user_name]
|
||||
* if %w(alice bob charlie).include? name
|
||||
* User.find_by("username = #{name}")
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the `include?` call guards against `name` taking arbitrary values when used
|
||||
* in the `find_by` call.
|
||||
*/
|
||||
deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
|
||||
CfgNodes::ExprNodes::MethodCallCfgNode {
|
||||
private CfgNode checkedNode;
|
||||
|
||||
|
||||
@@ -57,6 +57,24 @@ module SummaryComponent {
|
||||
*/
|
||||
SummaryComponent elementAny() { result = SC::content(TAnyElementContent()) }
|
||||
|
||||
/**
|
||||
* Gets a summary component that represents an element in a collection at known
|
||||
* integer index `lower` or above.
|
||||
*/
|
||||
SummaryComponent elementLowerBound(int lower) {
|
||||
result = SC::content(TElementLowerBoundContent(lower))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents a value in a pair at an unknown key. */
|
||||
SummaryComponent pairValueUnknown() {
|
||||
result = SC::content(TSingletonContent(TUnknownPairValueContent()))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents a value in a pair at a known key. */
|
||||
SummaryComponent pairValueKnown(ConstantValue key) {
|
||||
result = SC::content(TSingletonContent(TKnownPairValueContent(key)))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents the return value of a call. */
|
||||
SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) }
|
||||
}
|
||||
@@ -84,37 +102,10 @@ module SummaryComponentStack {
|
||||
}
|
||||
|
||||
/** A callable with a flow summary, identified by a unique string. */
|
||||
abstract class SummarizedCallable extends LibraryCallable {
|
||||
abstract class SummarizedCallable extends LibraryCallable, Impl::Public::SummarizedCallable {
|
||||
bindingset[this]
|
||||
SummarizedCallable() { any() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `input` to `output` through this callable.
|
||||
*
|
||||
* `preservesValue` indicates whether this is a value-preserving step
|
||||
* or a taint-step.
|
||||
*
|
||||
* Input specifications are restricted to stacks that end with
|
||||
* `SummaryComponent::argument(_)`, preceded by zero or more
|
||||
* `SummaryComponent::return()` or `SummaryComponent::content(_)` components.
|
||||
*
|
||||
* Output specifications are restricted to stacks that end with
|
||||
* `SummaryComponent::return()` or `SummaryComponent::argument(_)`.
|
||||
*
|
||||
* Output stacks ending with `SummaryComponent::return()` can be preceded by zero
|
||||
* or more `SummaryComponent::content(_)` components.
|
||||
*
|
||||
* Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
|
||||
* optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
|
||||
* by zero or more `SummaryComponent::content(_)` components.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as
|
||||
*
|
||||
@@ -143,18 +134,6 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
|
||||
final override MethodCall getACall() { result = mc }
|
||||
}
|
||||
|
||||
private class SummarizedCallableAdapter extends Impl::Public::SummarizedCallable {
|
||||
private SummarizedCallable sc;
|
||||
|
||||
SummarizedCallableAdapter() { this = TLibraryCallable(sc) }
|
||||
|
||||
final override predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
sc.propagatesFlow(input, output, preservesValue)
|
||||
}
|
||||
}
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
|
||||
private class SummarizedCallableFromModel extends SummarizedCallable {
|
||||
|
||||
@@ -120,11 +120,11 @@ class SummaryCall extends DataFlowCall, TSummaryCall {
|
||||
/** Gets the data flow node that this call targets. */
|
||||
DataFlow::Node getReceiver() { result = receiver }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result = c }
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
override string toString() { result = "[summary] call to " + receiver + " in " + c }
|
||||
|
||||
override Location getLocation() { result = c.getLocation() }
|
||||
override EmptyLocation getLocation() { any() }
|
||||
}
|
||||
|
||||
private class NormalCall extends DataFlowCall, TNormalCall {
|
||||
@@ -259,7 +259,10 @@ private module Cached {
|
||||
exists(any(Call c).getKeywordArgument(name))
|
||||
or
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
|
||||
}
|
||||
} or
|
||||
THashSplatArgumentPosition() or
|
||||
TAnyArgumentPosition() or
|
||||
TAnyKeywordArgumentPosition()
|
||||
|
||||
cached
|
||||
newtype TParameterPosition =
|
||||
@@ -278,7 +281,9 @@ private module Cached {
|
||||
or
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
|
||||
} or
|
||||
TAnyParameterPosition()
|
||||
THashSplatParameterPosition() or
|
||||
TAnyParameterPosition() or
|
||||
TAnyKeywordParameterPosition()
|
||||
}
|
||||
|
||||
import Cached
|
||||
@@ -476,12 +481,18 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a keyword parameter named `name`. */
|
||||
predicate isKeyword(string name) { this = TKeywordParameterPosition(name) }
|
||||
|
||||
/** Holds if this position represents a hash-splat parameter. */
|
||||
predicate isHashSplat() { this = THashSplatParameterPosition() }
|
||||
|
||||
/**
|
||||
* Holds if this position represents any parameter. This includes both positional
|
||||
* and named parameters.
|
||||
* Holds if this position represents any parameter, except `self` parameters. This
|
||||
* includes both positional, named, and block parameters.
|
||||
*/
|
||||
predicate isAny() { this = TAnyParameterPosition() }
|
||||
|
||||
/** Holds if this position represents any positional parameter. */
|
||||
predicate isAnyNamed() { this = TAnyKeywordParameterPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -494,7 +505,11 @@ class ParameterPosition extends TParameterPosition {
|
||||
or
|
||||
exists(string name | this.isKeyword(name) and result = "keyword " + name)
|
||||
or
|
||||
this.isHashSplat() and result = "**"
|
||||
or
|
||||
this.isAny() and result = "any"
|
||||
or
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,6 +527,21 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
/** Holds if this position represents a keyword argument named `name`. */
|
||||
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
|
||||
|
||||
/**
|
||||
* Holds if this position represents any argument, except `self` arguments. This
|
||||
* includes both positional, named, and block arguments.
|
||||
*/
|
||||
predicate isAny() { this = TAnyArgumentPosition() }
|
||||
|
||||
/** Holds if this position represents any positional parameter. */
|
||||
predicate isAnyNamed() { this = TAnyKeywordArgumentPosition() }
|
||||
|
||||
/**
|
||||
* Holds if this position represents a synthesized argument containing all keyword
|
||||
* arguments wrapped in a hash.
|
||||
*/
|
||||
predicate isHashSplat() { this = THashSplatArgumentPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -521,11 +551,17 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
exists(int pos | this.isPositional(pos) and result = "position " + pos)
|
||||
or
|
||||
exists(string name | this.isKeyword(name) and result = "keyword " + name)
|
||||
or
|
||||
this.isAny() and result = "any"
|
||||
or
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
or
|
||||
this.isHashSplat() and result = "**"
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[inline]
|
||||
pragma[nomagic]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
ppos.isSelf() and apos.isSelf()
|
||||
or
|
||||
@@ -539,5 +575,13 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
|
||||
or
|
||||
ppos.isAny() and exists(apos)
|
||||
ppos.isHashSplat() and apos.isHashSplat()
|
||||
or
|
||||
ppos.isAny() and not apos.isSelf()
|
||||
or
|
||||
apos.isAny() and not ppos.isSelf()
|
||||
or
|
||||
ppos.isAnyNamed() and apos.isKeyword(_)
|
||||
or
|
||||
apos.isAnyNamed() and ppos.isKeyword(_)
|
||||
}
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -216,10 +216,9 @@ private module LambdaFlow {
|
||||
or
|
||||
// jump step
|
||||
exists(Node mid, DataFlowType t0 |
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, lastCall) and
|
||||
toReturn = false and
|
||||
toJump = true and
|
||||
lastCall = TDataFlowCallNone()
|
||||
toJump = true
|
||||
|
|
||||
jumpStepCached(node, mid) and
|
||||
t = t0
|
||||
@@ -305,7 +304,7 @@ cached
|
||||
private module Cached {
|
||||
/**
|
||||
* If needed, call this predicate from `DataFlowImplSpecific.qll` in order to
|
||||
* force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby
|
||||
* force a stage-dependency on the `DataFlowImplCommon.qll` stage and thereby
|
||||
* collapsing the two stages.
|
||||
*/
|
||||
cached
|
||||
@@ -789,24 +788,31 @@ private module Cached {
|
||||
cached
|
||||
predicate readSet(Node node1, ContentSet c, Node node2) { readStep(node1, c, node2) }
|
||||
|
||||
cached
|
||||
predicate storeSet(
|
||||
Node node1, ContentSet c, Node node2, DataFlowType contentType, DataFlowType containerType
|
||||
) {
|
||||
storeStep(node1, c, node2) and
|
||||
contentType = getNodeDataFlowType(node1) and
|
||||
containerType = getNodeDataFlowType(node2)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1)
|
||||
or
|
||||
readSet(n2, c, n1) and
|
||||
contentType = getNodeDataFlowType(n1) and
|
||||
containerType = getNodeDataFlowType(n2)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate store(
|
||||
Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType
|
||||
) {
|
||||
exists(ContentSet cs | c = cs.getAStoreContent() |
|
||||
storeStep(node1, cs, node2) and
|
||||
contentType = getNodeDataFlowType(node1) and
|
||||
containerType = getNodeDataFlowType(node2)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, cs, contentType), n1)
|
||||
or
|
||||
readSet(n2, cs, n1) and
|
||||
contentType = getNodeDataFlowType(n1) and
|
||||
containerType = getNodeDataFlowType(n2)
|
||||
)
|
||||
exists(ContentSet cs |
|
||||
c = cs.getAStoreContent() and storeSet(node1, cs, node2, contentType, containerType)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ private import DataFlowPublic
|
||||
private import DataFlowDispatch
|
||||
private import SsaImpl as SsaImpl
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
|
||||
@@ -177,7 +179,8 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
exists(int i |
|
||||
this = call.getArgument(i) and
|
||||
not this.getExpr() instanceof BlockArgument and
|
||||
not exists(this.getExpr().(Pair).getKey().getConstantValue().getSymbol()) and
|
||||
not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and
|
||||
not this.getExpr() instanceof HashSplatExpr and
|
||||
arg.isPositional(i)
|
||||
)
|
||||
or
|
||||
@@ -188,6 +191,10 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
)
|
||||
or
|
||||
this = call.getReceiver() and arg.isSelf()
|
||||
or
|
||||
this = call.getAnArgument() and
|
||||
this.getExpr() instanceof HashSplatExpr and
|
||||
arg.isHashSplat()
|
||||
}
|
||||
|
||||
/** Holds if this expression is the `i`th argument of `c`. */
|
||||
@@ -199,9 +206,11 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
/** A collection of cached types and predicates to be evaluated in the same stage. */
|
||||
cached
|
||||
private module Cached {
|
||||
private import TaintTrackingPrivate as TaintTrackingPrivate
|
||||
|
||||
cached
|
||||
newtype TNode =
|
||||
TExprNode(CfgNodes::ExprCfgNode n) or
|
||||
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
|
||||
TReturningNode(CfgNodes::ReturningCfgNode n) or
|
||||
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
|
||||
exists(ReturningNode ret |
|
||||
@@ -213,11 +222,15 @@ private module Cached {
|
||||
TNormalParameterNode(Parameter p) {
|
||||
p instanceof SimpleParameter or
|
||||
p instanceof OptionalParameter or
|
||||
p instanceof KeywordParameter
|
||||
p instanceof KeywordParameter or
|
||||
p instanceof HashSplatParameter
|
||||
} or
|
||||
TSelfParameterNode(MethodBase m) or
|
||||
TBlockParameterNode(MethodBase m) or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) { n instanceof Argument } or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
|
||||
n instanceof Argument or
|
||||
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
|
||||
} or
|
||||
TSummaryNode(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c,
|
||||
FlowSummaryImpl::Private::SummaryNodeState state
|
||||
@@ -226,6 +239,9 @@ private module Cached {
|
||||
} or
|
||||
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
|
||||
} or
|
||||
THashSplatArgumentsNode(CfgNodes::ExprNodes::CallCfgNode c) {
|
||||
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
|
||||
}
|
||||
|
||||
class TParameterNode =
|
||||
@@ -252,7 +268,7 @@ private module Cached {
|
||||
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom)
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
@@ -270,7 +286,7 @@ private module Cached {
|
||||
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, true)
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
|
||||
}
|
||||
|
||||
/** This is the local flow predicate that is used in type tracking. */
|
||||
@@ -296,6 +312,20 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachedFromExprOrEntrySsaDef(Node n) {
|
||||
localFlowStepTypeTracker(any(Node n0 |
|
||||
n0 instanceof ExprNode
|
||||
or
|
||||
entrySsaDefinition(n0)
|
||||
), n)
|
||||
or
|
||||
exists(Node mid |
|
||||
reachedFromExprOrEntrySsaDef(mid) and
|
||||
localFlowStepTypeTracker(mid, n)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate isLocalSourceNode(Node n) {
|
||||
n instanceof ParameterNode
|
||||
@@ -303,11 +333,7 @@ private module Cached {
|
||||
n instanceof PostUpdateNodes::ExprPostUpdateNode
|
||||
or
|
||||
// Nodes that can't be reached from another entry definition or expression.
|
||||
not localFlowStepTypeTracker+(any(Node n0 |
|
||||
n0 instanceof ExprNode
|
||||
or
|
||||
entrySsaDefinition(n0)
|
||||
), n)
|
||||
not reachedFromExprOrEntrySsaDef(n)
|
||||
or
|
||||
// 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,
|
||||
@@ -319,7 +345,10 @@ private module Cached {
|
||||
cached
|
||||
newtype TContentSet =
|
||||
TSingletonContent(Content c) or
|
||||
TAnyElementContent()
|
||||
TAnyElementContent() or
|
||||
TElementLowerBoundContent(int lower) {
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedElementLowerBoundPosition(_, lower)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TContent =
|
||||
@@ -327,7 +356,25 @@ private module Cached {
|
||||
not cv.isInt(_) or
|
||||
cv.getInt() in [0 .. 10]
|
||||
} or
|
||||
TUnknownElementContent()
|
||||
TUnknownElementContent() or
|
||||
TKnownPairValueContent(ConstantValue cv) or
|
||||
TUnknownPairValueContent() or
|
||||
TFieldContent(string name) {
|
||||
name = any(InstanceVariable v).getName()
|
||||
or
|
||||
name = "@" + any(SetterMethodCall c).getTargetName()
|
||||
or
|
||||
// The following equation unfortunately leads to a non-monotonic recursion error:
|
||||
// name = any(AccessPathToken a).getAnArgument("Field")
|
||||
// Therefore, we use the following instead to extract the field names from the
|
||||
// external model data. This, unfortunately, does not included any field names used
|
||||
// in models defined in QL code.
|
||||
exists(string input, string output |
|
||||
ModelOutput::relevantSummaryModel(_, _, _, input, output, _)
|
||||
|
|
||||
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
|
||||
@@ -346,6 +393,8 @@ private module Cached {
|
||||
|
||||
class TElementContent = TKnownElementContent or TUnknownElementContent;
|
||||
|
||||
class TPairValueContent = TKnownPairValueContent or TUnknownPairValueContent;
|
||||
|
||||
import Cached
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
@@ -365,6 +414,8 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SummaryParameterNode
|
||||
or
|
||||
n instanceof SynthReturnNode
|
||||
or
|
||||
n instanceof HashSplatArgumentsNode
|
||||
}
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
@@ -449,6 +500,9 @@ private module ParameterNodes {
|
||||
c.getAParameter() = kp and
|
||||
pos.isKeyword(kp.getName())
|
||||
)
|
||||
or
|
||||
parameter = c.getAParameter().(HashSplatParameter) and
|
||||
pos.isHashSplat()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = parameter.getCallable() }
|
||||
@@ -528,12 +582,12 @@ private module ParameterNodes {
|
||||
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
sc = c and pos = pos_
|
||||
sc = c.asLibraryCallable() and pos = pos_
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result = sc }
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
|
||||
|
||||
override EmptyLocation getLocationImpl() { any() }
|
||||
|
||||
@@ -552,7 +606,7 @@ class SummaryNode extends NodeImpl, TSummaryNode {
|
||||
|
||||
override CfgScope getCfgScope() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result = c }
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
override EmptyLocation getLocationImpl() { any() }
|
||||
|
||||
@@ -627,6 +681,40 @@ private module ArgumentNodes {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that represents all keyword arguments wrapped in a hash.
|
||||
*
|
||||
* The callee is responsible for filtering out the keyword arguments that are
|
||||
* part of the method signature, such that those cannot end up in the hash-splat
|
||||
* parameter.
|
||||
*/
|
||||
class HashSplatArgumentsNode extends ArgumentNode, THashSplatArgumentsNode {
|
||||
CfgNodes::ExprNodes::CallCfgNode c;
|
||||
|
||||
HashSplatArgumentsNode() { this = THashSplatArgumentsNode(c) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.sourceArgumentOf(call.asCall(), pos)
|
||||
}
|
||||
|
||||
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
|
||||
call = c and
|
||||
pos.isHashSplat()
|
||||
}
|
||||
}
|
||||
|
||||
private class HashSplatArgumentsNodeImpl extends NodeImpl, THashSplatArgumentsNode {
|
||||
CfgNodes::ExprNodes::CallCfgNode c;
|
||||
|
||||
HashSplatArgumentsNodeImpl() { this = THashSplatArgumentsNode(c) }
|
||||
|
||||
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
|
||||
|
||||
override Location getLocationImpl() { result = c.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "**" }
|
||||
}
|
||||
}
|
||||
|
||||
import ArgumentNodes
|
||||
@@ -783,11 +871,97 @@ predicate jumpStep(Node pred, Node succ) {
|
||||
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
|
||||
}
|
||||
|
||||
predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
|
||||
private ContentSet getKeywordContent(string name) {
|
||||
exists(ConstantValue::ConstantSymbolValue key |
|
||||
result.isSingleton(TKnownElementContent(key)) and
|
||||
key.isSymbol(name)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via an assignment to
|
||||
* content `c`.
|
||||
*/
|
||||
predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
// Instance variable assignment, `@var = src`
|
||||
node2.(PostUpdateNode).getPreUpdateNode().asExpr() =
|
||||
any(CfgNodes::ExprNodes::InstanceVariableWriteAccessCfgNode var |
|
||||
exists(CfgNodes::ExprNodes::AssignExprCfgNode assign |
|
||||
var = assign.getLhs() and
|
||||
node1.asExpr() = assign.getRhs()
|
||||
|
|
||||
c.isSingleton(any(Content::FieldContent ct |
|
||||
ct.getName() = var.getExpr().getVariable().getName()
|
||||
))
|
||||
)
|
||||
).getReceiver()
|
||||
or
|
||||
// Attribute assignment, `receiver.property = value`
|
||||
node2.(PostUpdateNode).getPreUpdateNode().asExpr() =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
|
||||
node1.asExpr() = call.getArgument(0) and
|
||||
call.getNumberOfArguments() = 1 and
|
||||
c.isSingleton(any(Content::FieldContent ct |
|
||||
ct.getName() = "@" + call.getExpr().(SetterMethodCall).getTargetName()
|
||||
))
|
||||
).getReceiver()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
|
||||
or
|
||||
// Needed for pairs passed into method calls where the key is not a symbol,
|
||||
// that is, where it is not a keyword argument.
|
||||
node2.asExpr() =
|
||||
any(CfgNodes::ExprNodes::PairCfgNode pair |
|
||||
exists(CfgNodes::ExprCfgNode key |
|
||||
key = pair.getKey() and
|
||||
pair.getValue() = node1.asExpr()
|
||||
|
|
||||
exists(ConstantValue cv |
|
||||
cv = key.getConstantValue() and
|
||||
not cv.isSymbol(_) and // handled as a keyword argument
|
||||
c.isSingleton(TKnownPairValueContent(cv))
|
||||
)
|
||||
or
|
||||
not exists(key.getConstantValue()) and
|
||||
c.isSingleton(TUnknownPairValueContent())
|
||||
)
|
||||
)
|
||||
or
|
||||
// Wrap all keyword arguments in a synthesized hash-splat argument node
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition keywordPos, string name |
|
||||
node2 = THashSplatArgumentsNode(call) and
|
||||
node1.asExpr().(Argument).isArgumentOf(call, keywordPos) and
|
||||
keywordPos.isKeyword(name) and
|
||||
c = getKeywordContent(name)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a read step of content `c` from `node1` to `node2`.
|
||||
*/
|
||||
predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
// Instance variable read access, `@var`
|
||||
node2.asExpr() =
|
||||
any(CfgNodes::ExprNodes::InstanceVariableReadAccessCfgNode var |
|
||||
node1.asExpr() = var.getReceiver() and
|
||||
c.isSingleton(any(Content::FieldContent ct |
|
||||
ct.getName() = var.getExpr().getVariable().getName()
|
||||
))
|
||||
)
|
||||
or
|
||||
// Attribute read, `receiver.field`. Note that we do not check whether
|
||||
// the `field` method is really an attribute reader. This is probably fine
|
||||
// because the read step has only effect if there exists a matching store step
|
||||
// (instance variable assignment or setter method call).
|
||||
node2.asExpr() =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
|
||||
node1.asExpr() = call.getReceiver() and
|
||||
call.getNumberOfArguments() = 0 and
|
||||
c.isSingleton(any(Content::FieldContent ct |
|
||||
ct.getName() = "@" + call.getExpr().getMethodName()
|
||||
))
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
|
||||
}
|
||||
|
||||
@@ -798,6 +972,19 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
*/
|
||||
predicate clearsContent(Node n, ContentSet c) {
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
|
||||
or
|
||||
// Filter out keyword arguments that are part of the method signature from
|
||||
// the hash-splat parameter
|
||||
exists(
|
||||
DataFlowCallable callable, ParameterPosition hashSplatPos, ParameterNodeImpl keywordParam,
|
||||
ParameterPosition keywordPos, string name
|
||||
|
|
||||
n.(ParameterNodes::NormalParameterNode).isParameterOf(callable, hashSplatPos) and
|
||||
hashSplatPos.isHashSplat() and
|
||||
keywordParam.isParameterOf(callable, keywordPos) and
|
||||
keywordPos.isKeyword(name) and
|
||||
c = getKeywordContent(name)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -223,6 +223,18 @@ module Content {
|
||||
override string toString() { result = "element" }
|
||||
}
|
||||
|
||||
/** A field of an object, for example an instance variable. */
|
||||
class FieldContent extends Content, TFieldContent {
|
||||
private string name;
|
||||
|
||||
FieldContent() { this = TFieldContent(name) }
|
||||
|
||||
/** Gets the name of the field. */
|
||||
string getName() { result = name }
|
||||
|
||||
override string toString() { result = name }
|
||||
}
|
||||
|
||||
/** Gets the element content corresponding to constant value `cv`. */
|
||||
ElementContent getElementContent(ConstantValue cv) {
|
||||
result = TKnownElementContent(cv)
|
||||
@@ -230,6 +242,35 @@ module Content {
|
||||
not exists(TKnownElementContent(cv)) and
|
||||
result = TUnknownElementContent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constant value of `e`, which corresponds to a valid known
|
||||
* element index. Unlike calling simply `e.getConstantValue()`, this
|
||||
* excludes negative array indices.
|
||||
*/
|
||||
ConstantValue getKnownElementIndex(Expr e) {
|
||||
result = getElementContent(e.getConstantValue()).(KnownElementContent).getIndex()
|
||||
}
|
||||
|
||||
/** A value in a pair with a known or unknown key. */
|
||||
class PairValueContent extends Content, TPairValueContent { }
|
||||
|
||||
/** A value in a pair with a known key. */
|
||||
class KnownPairValueContent extends PairValueContent, TKnownPairValueContent {
|
||||
private ConstantValue key;
|
||||
|
||||
KnownPairValueContent() { this = TKnownPairValueContent(key) }
|
||||
|
||||
/** Gets the index in the collection. */
|
||||
ConstantValue getIndex() { result = key }
|
||||
|
||||
override string toString() { result = "pair " + key }
|
||||
}
|
||||
|
||||
/** A value in a pair with an unknown key. */
|
||||
class UnknownPairValueContent extends PairValueContent, TUnknownPairValueContent {
|
||||
override string toString() { result = "pair" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,6 +286,12 @@ class ContentSet extends TContentSet {
|
||||
/** Holds if this content set represents all `ElementContent`s. */
|
||||
predicate isAnyElement() { this = TAnyElementContent() }
|
||||
|
||||
/**
|
||||
* Holds if this content set represents all `KnownElementContent`s where
|
||||
* the index is an integer greater than or equal to `lower`.
|
||||
*/
|
||||
predicate isElementLowerBound(int lower) { this = TElementLowerBoundContent(lower) }
|
||||
|
||||
/** Gets a textual representation of this content set. */
|
||||
string toString() {
|
||||
exists(Content c |
|
||||
@@ -253,7 +300,12 @@ class ContentSet extends TContentSet {
|
||||
)
|
||||
or
|
||||
this.isAnyElement() and
|
||||
result = "any array element"
|
||||
result = "any element"
|
||||
or
|
||||
exists(int lower |
|
||||
this.isElementLowerBound(lower) and
|
||||
result = lower + ".."
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a content that may be stored into when storing into this set. */
|
||||
@@ -262,6 +314,9 @@ class ContentSet extends TContentSet {
|
||||
or
|
||||
this.isAnyElement() and
|
||||
result = TUnknownElementContent()
|
||||
or
|
||||
this.isElementLowerBound(_) and
|
||||
result = TUnknownElementContent()
|
||||
}
|
||||
|
||||
/** Gets a content that may be read from when reading from this set. */
|
||||
@@ -270,9 +325,75 @@ class ContentSet extends TContentSet {
|
||||
or
|
||||
this.isAnyElement() and
|
||||
result instanceof Content::ElementContent
|
||||
or
|
||||
exists(int lower, int i |
|
||||
this.isElementLowerBound(lower) and
|
||||
result.(Content::KnownElementContent).getIndex().isInt(i) and
|
||||
i >= lower
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the guard `g` validates the expression `e` upon evaluating to `branch`.
|
||||
*
|
||||
* The expression `e` is expected to be a syntactic part of the guard `g`.
|
||||
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
|
||||
* the argument `x`.
|
||||
*/
|
||||
signature predicate guardChecksSig(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch);
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates an expression.
|
||||
*
|
||||
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
|
||||
* in data flow and taint tracking.
|
||||
*/
|
||||
module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
/** Gets a node that is safely guarded by the given guard check. */
|
||||
Node getABarrierNode() {
|
||||
exists(
|
||||
CfgNodes::ExprCfgNode g, boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
def.getARead() = result.asExpr() and
|
||||
guardChecks(g, testedNode, branch) and
|
||||
guardControlsBlock(g, result.asExpr().getBasicBlock(), branch)
|
||||
)
|
||||
or
|
||||
result.asExpr() = getAMaybeGuardedCapturedDef().getARead()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an implicit entry definition for a captured variable that
|
||||
* may be guarded, because a call to the capturing callable is guarded.
|
||||
*
|
||||
* This is restricted to calls where the variable is captured inside a
|
||||
* block.
|
||||
*/
|
||||
private Ssa::Definition getAMaybeGuardedCapturedDef() {
|
||||
exists(
|
||||
CfgNodes::ExprCfgNode g, boolean branch, CfgNodes::ExprCfgNode testedNode,
|
||||
Ssa::Definition def, CfgNodes::ExprNodes::CallCfgNode call
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
guardChecks(g, testedNode, branch) and
|
||||
SsaImpl::captureFlowIn(call, def, result) and
|
||||
guardControlsBlock(g, call.getBasicBlock(), branch) and
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */
|
||||
private predicate guardControlsBlock(CfgNodes::ExprCfgNode guard, BasicBlock bb, boolean branch) {
|
||||
exists(ConditionBlock conditionBlock, SuccessorTypes::BooleanSuccessor s |
|
||||
guard = conditionBlock.getLastNode() and
|
||||
s.getValue() = branch and
|
||||
conditionBlock.controls(bb, s)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A guard that validates some expression.
|
||||
*
|
||||
@@ -282,7 +403,7 @@ class ContentSet extends TContentSet {
|
||||
*
|
||||
* It is important that all extending classes in scope are disjoint.
|
||||
*/
|
||||
abstract class BarrierGuard extends CfgNodes::ExprCfgNode {
|
||||
abstract deprecated class BarrierGuard extends CfgNodes::ExprCfgNode {
|
||||
private ConditionBlock conditionBlock;
|
||||
|
||||
BarrierGuard() { this = conditionBlock.getLastNode() }
|
||||
|
||||
@@ -195,7 +195,10 @@ module Public {
|
||||
}
|
||||
|
||||
/** A callable with a flow summary. */
|
||||
abstract class SummarizedCallable extends DataFlowCallable {
|
||||
abstract class SummarizedCallable extends SummarizedCallableBase {
|
||||
bindingset[this]
|
||||
SummarizedCallable() { any() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `input` to `output` through this callable.
|
||||
*
|
||||
@@ -493,7 +496,7 @@ module Private {
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
parameterReadState(c, state, pos) and
|
||||
result.(ParamNode).isParameterOf(c, pos)
|
||||
result.(ParamNode).isParameterOf(inject(c), pos)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -621,7 +624,7 @@ module Private {
|
||||
predicate summaryPostUpdateNode(Node post, Node pre) {
|
||||
exists(SummarizedCallable c, ParameterPosition pos |
|
||||
isParameterPostUpdate(post, c, pos) and
|
||||
pre.(ParamNode).isParameterOf(c, pos)
|
||||
pre.(ParamNode).isParameterOf(inject(c), pos)
|
||||
)
|
||||
or
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s |
|
||||
@@ -644,7 +647,7 @@ module Private {
|
||||
* node, and back out to `p`.
|
||||
*/
|
||||
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
|
||||
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(c, ppos) |
|
||||
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(inject(c), ppos) |
|
||||
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
|
||||
summary(c, inputContents, outputContents, _) and
|
||||
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and
|
||||
@@ -748,13 +751,16 @@ module Private {
|
||||
private predicate viableParam(
|
||||
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, ParamNode p
|
||||
) {
|
||||
p.isParameterOf(sc, ppos) and
|
||||
sc = viableCallable(call)
|
||||
exists(DataFlowCallable c |
|
||||
c = inject(sc) and
|
||||
p.isParameterOf(c, ppos) and
|
||||
c = viableCallable(call)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg) {
|
||||
exists(ParameterPosition ppos, SummarizedCallable sc |
|
||||
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
|
||||
exists(ParameterPosition ppos |
|
||||
argumentPositionMatch(call, arg, ppos) and
|
||||
viableParam(call, sc, ppos, result)
|
||||
)
|
||||
@@ -768,41 +774,56 @@ module Private {
|
||||
* or expects contents.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate prohibitsUseUseFlow(ArgNode arg) {
|
||||
predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) {
|
||||
exists(ParamNode p, Node mid, ParameterPosition ppos, Node ret |
|
||||
p = summaryArgParam0(_, arg) and
|
||||
p.isParameterOf(_, ppos) and
|
||||
p = summaryArgParam0(_, arg, sc) and
|
||||
p.isParameterOf(_, pragma[only_bind_into](ppos)) and
|
||||
summaryLocalStep(p, mid, true) and
|
||||
summaryLocalStep(mid, ret, true) and
|
||||
isParameterPostUpdate(ret, _, ppos)
|
||||
isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos))
|
||||
|
|
||||
summaryClearsContent(mid, _) or
|
||||
summaryExpectsContent(mid, _)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ParamNode summaryArgParam(ArgNode arg, ReturnKindExt rk, OutNodeExt out) {
|
||||
exists(DataFlowCall call |
|
||||
result = summaryArgParam0(call, arg) and
|
||||
out = rk.getAnOutNode(call)
|
||||
bindingset[ret]
|
||||
private ParamNode summaryArgParam(
|
||||
ArgNode arg, ReturnNodeExt ret, OutNodeExt out, SummarizedCallable sc
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKindExt rk |
|
||||
result = summaryArgParam0(call, arg, sc) and
|
||||
ret.getKind() = pragma[only_bind_into](rk) and
|
||||
out = pragma[only_bind_into](rk).getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` using a simple flow summary, that is, a flow
|
||||
* summary without reads and stores.
|
||||
* Holds if `arg` flows to `out` using a simple value-preserving flow
|
||||
* summary, that is, a flow summary without reads and stores.
|
||||
*
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryThroughStep(ArgNode arg, Node out, boolean preservesValue) {
|
||||
exists(ReturnKindExt rk, ReturnNodeExt ret |
|
||||
summaryLocalStep(summaryArgParam(arg, rk, out), ret, preservesValue) and
|
||||
ret.getKind() = rk
|
||||
predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) {
|
||||
exists(ReturnKind rk, ReturnNode ret, DataFlowCall call |
|
||||
summaryLocalStep(summaryArgParam0(call, arg, sc), ret, true) and
|
||||
ret.getKind() = pragma[only_bind_into](rk) and
|
||||
out = getAnOutNode(call, pragma[only_bind_into](rk))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` using a simple flow summary involving taint
|
||||
* step, that is, a flow summary without reads and stores.
|
||||
*
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) {
|
||||
exists(ReturnNodeExt ret | summaryLocalStep(summaryArgParam(arg, ret, out, sc), ret, false))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a read(+taint) of `c` from `arg` to `out` using a
|
||||
* flow summary.
|
||||
@@ -810,11 +831,10 @@ module Private {
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out) {
|
||||
exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret |
|
||||
summaryReadStep(summaryArgParam(arg, rk, out), c, mid) and
|
||||
summaryLocalStep(mid, ret, _) and
|
||||
ret.getKind() = rk
|
||||
predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
|
||||
exists(Node mid, ReturnNodeExt ret |
|
||||
summaryReadStep(summaryArgParam(arg, ret, out, sc), c, mid) and
|
||||
summaryLocalStep(mid, ret, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -825,11 +845,10 @@ module Private {
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summarySetterStep(ArgNode arg, ContentSet c, Node out) {
|
||||
exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret |
|
||||
summaryLocalStep(summaryArgParam(arg, rk, out), mid, _) and
|
||||
summaryStoreStep(mid, c, ret) and
|
||||
ret.getKind() = rk
|
||||
predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
|
||||
exists(Node mid, ReturnNodeExt ret |
|
||||
summaryLocalStep(summaryArgParam(arg, ret, out, sc), mid, _) and
|
||||
summaryStoreStep(mid, c, ret)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -912,11 +931,18 @@ module Private {
|
||||
private class SummarizedCallableExternal extends SummarizedCallable {
|
||||
SummarizedCallableExternal() { summaryElement(this, _, _, _, _) }
|
||||
|
||||
private predicate relevantSummaryElementGenerated(
|
||||
AccessPath inSpec, AccessPath outSpec, string kind
|
||||
) {
|
||||
summaryElement(this, inSpec, outSpec, kind, true) and
|
||||
not summaryElement(this, _, _, _, false) and
|
||||
not this.clearsContent(_, _)
|
||||
}
|
||||
|
||||
private predicate relevantSummaryElement(AccessPath inSpec, AccessPath outSpec, string kind) {
|
||||
summaryElement(this, inSpec, outSpec, kind, false)
|
||||
or
|
||||
summaryElement(this, inSpec, outSpec, kind, true) and
|
||||
not summaryElement(this, _, _, _, false)
|
||||
this.relevantSummaryElementGenerated(inSpec, outSpec, kind)
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(
|
||||
@@ -933,7 +959,7 @@ module Private {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAutoGenerated() { summaryElement(this, _, _, _, true) }
|
||||
override predicate isAutoGenerated() { this.relevantSummaryElementGenerated(_, _, _) }
|
||||
}
|
||||
|
||||
/** Holds if component `c` of specification `spec` cannot be parsed. */
|
||||
@@ -1069,7 +1095,7 @@ module Private {
|
||||
/** Provides a query predicate for outputting a set of relevant flow summaries. */
|
||||
module TestOutput {
|
||||
/** A flow summary to include in the `summary/3` query predicate. */
|
||||
abstract class RelevantSummarizedCallable extends SummarizedCallable {
|
||||
abstract class RelevantSummarizedCallable instanceof SummarizedCallable {
|
||||
/** Gets the string representation of this callable used by `summary/1`. */
|
||||
abstract string getCallableCsv();
|
||||
|
||||
@@ -1077,8 +1103,10 @@ module Private {
|
||||
predicate relevantSummary(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
this.propagatesFlow(input, output, preservesValue)
|
||||
super.propagatesFlow(input, output, preservesValue)
|
||||
}
|
||||
|
||||
string toString() { result = super.toString() }
|
||||
}
|
||||
|
||||
/** Render the kind in the format used in flow summaries. */
|
||||
@@ -1088,13 +1116,13 @@ module Private {
|
||||
preservesValue = false and result = "taint"
|
||||
}
|
||||
|
||||
private string renderGenerated(RelevantSummarizedCallable c) {
|
||||
if c.isAutoGenerated() then result = "generated:" else result = ""
|
||||
private string renderProvenance(RelevantSummarizedCallable c) {
|
||||
if c.(SummarizedCallable).isAutoGenerated() then result = "generated" else result = "manual"
|
||||
}
|
||||
|
||||
/**
|
||||
* A query predicate for outputting flow summaries in semi-colon separated format in QL tests.
|
||||
* The syntax is: "namespace;type;overrides;name;signature;ext;inputspec;outputspec;(generated:)?kind",
|
||||
* The syntax is: "namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind;provenance"",
|
||||
* ext is hardcoded to empty.
|
||||
*/
|
||||
query predicate summary(string csv) {
|
||||
@@ -1105,7 +1133,7 @@ module Private {
|
||||
c.relevantSummary(input, output, preservesValue) and
|
||||
csv =
|
||||
c.getCallableCsv() + getComponentStackCsv(input) + ";" + getComponentStackCsv(output) +
|
||||
";" + renderGenerated(c) + renderKind(preservesValue)
|
||||
";" + renderKind(preservesValue) + ";" + renderProvenance(c)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1119,19 +1147,21 @@ module Private {
|
||||
*/
|
||||
module RenderSummarizedCallable {
|
||||
/** A summarized callable to include in the graph. */
|
||||
abstract class RelevantSummarizedCallable extends SummarizedCallable { }
|
||||
abstract class RelevantSummarizedCallable instanceof SummarizedCallable {
|
||||
string toString() { result = super.toString() }
|
||||
}
|
||||
|
||||
private newtype TNodeOrCall =
|
||||
MkNode(Node n) {
|
||||
exists(RelevantSummarizedCallable c |
|
||||
n = summaryNode(c, _)
|
||||
or
|
||||
n.(ParamNode).isParameterOf(c, _)
|
||||
n.(ParamNode).isParameterOf(inject(c), _)
|
||||
)
|
||||
} or
|
||||
MkCall(DataFlowCall call) {
|
||||
call = summaryDataFlowCall(_) and
|
||||
call.getEnclosingCallable() instanceof RelevantSummarizedCallable
|
||||
call.getEnclosingCallable() = inject(any(RelevantSummarizedCallable c))
|
||||
}
|
||||
|
||||
private class NodeOrCall extends TNodeOrCall {
|
||||
|
||||
@@ -11,6 +11,10 @@ private import FlowSummaryImpl::Private
|
||||
private import FlowSummaryImpl::Public
|
||||
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
|
||||
|
||||
class SummarizedCallableBase = string;
|
||||
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
|
||||
|
||||
/** Gets the parameter position of the instance parameter. */
|
||||
ArgumentPosition instanceParameterPosition() { none() } // disables implicit summary flow to `self` for callbacks
|
||||
|
||||
@@ -46,11 +50,10 @@ DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() }
|
||||
* stating whether the summary is autogenerated.
|
||||
*/
|
||||
predicate summaryElement(
|
||||
DataFlowCallable c, string input, string output, string kind, boolean generated
|
||||
FlowSummary::SummarizedCallable c, string input, string output, string kind, boolean generated
|
||||
) {
|
||||
exists(FlowSummary::SummarizedCallable sc, boolean preservesValue |
|
||||
sc.propagatesFlowExt(input, output, preservesValue) and
|
||||
c.asLibraryCallable() = sc and
|
||||
exists(boolean preservesValue |
|
||||
c.propagatesFlowExt(input, output, preservesValue) and
|
||||
(if preservesValue = true then kind = "value" else kind = "taint") and
|
||||
generated = false
|
||||
)
|
||||
@@ -64,6 +67,11 @@ private SummaryComponent interpretElementArg(string arg) {
|
||||
arg = "any" and
|
||||
result = FlowSummary::SummaryComponent::elementAny()
|
||||
or
|
||||
exists(int lower |
|
||||
ParsePositions::isParsedElementLowerBoundPosition(arg, lower) and
|
||||
result = FlowSummary::SummaryComponent::elementLowerBound(lower)
|
||||
)
|
||||
or
|
||||
exists(ConstantValue cv | result = FlowSummary::SummaryComponent::elementKnown(cv) |
|
||||
cv.isInt(AccessPath::parseInt(arg))
|
||||
or
|
||||
@@ -86,10 +94,16 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
ppos.isAny()
|
||||
or
|
||||
ppos.isPositionalLowerBound(AccessPath::parseLowerBound(arg))
|
||||
or
|
||||
arg = "hash-splat" and
|
||||
ppos.isHashSplat()
|
||||
)
|
||||
or
|
||||
result = interpretElementArg(c.getAnArgument("Element"))
|
||||
or
|
||||
result =
|
||||
FlowSummary::SummaryComponent::content(TSingletonContent(TFieldContent(c.getAnArgument("Field"))))
|
||||
or
|
||||
exists(ContentSet cs |
|
||||
FlowSummary::SummaryComponent::content(cs) = interpretElementArg(c.getAnArgument("WithElement")) and
|
||||
result = FlowSummary::SummaryComponent::withContent(cs)
|
||||
@@ -100,6 +114,16 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
interpretElementArg(c.getAnArgument("WithoutElement")) and
|
||||
result = FlowSummary::SummaryComponent::withoutContent(cs)
|
||||
)
|
||||
or
|
||||
exists(string arg | arg = c.getAnArgument("PairValue") |
|
||||
arg = "?" and
|
||||
result = FlowSummary::SummaryComponent::pairValueUnknown()
|
||||
or
|
||||
exists(ConstantValue cv |
|
||||
result = FlowSummary::SummaryComponent::pairValueKnown(cv) and
|
||||
cv.serialize() = arg
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for flow summaries. */
|
||||
@@ -214,6 +238,13 @@ module ParsePositions {
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isElementBody(string body) {
|
||||
exists(AccessPathToken tok |
|
||||
tok.getName() = "Element" and
|
||||
body = tok.getAnArgument()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isParsedParameterPosition(string c, int i) {
|
||||
isParamBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
@@ -238,6 +269,11 @@ module ParsePositions {
|
||||
isArgBody(c) and
|
||||
c = paramName + ":"
|
||||
}
|
||||
|
||||
predicate isParsedElementLowerBoundPosition(string c, int lower) {
|
||||
isElementBody(c) and
|
||||
lower = AccessPath::parseLowerBound(c)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */
|
||||
@@ -257,6 +293,12 @@ ArgumentPosition parseParamBody(string s) {
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
or
|
||||
s = "any" and
|
||||
result.isAny()
|
||||
or
|
||||
s = "any-named" and
|
||||
result.isAnyNamed()
|
||||
}
|
||||
|
||||
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
|
||||
@@ -281,4 +323,10 @@ ParameterPosition parseArgBody(string s) {
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
or
|
||||
s = "any" and
|
||||
result.isAny()
|
||||
or
|
||||
s = "any-named" and
|
||||
result.isAnyNamed()
|
||||
}
|
||||
|
||||
@@ -11,12 +11,6 @@ private import FlowSummaryImpl as FlowSummaryImpl
|
||||
*/
|
||||
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `guard` should be a sanitizer guard in all global taint flow configurations
|
||||
* but not in local taint.
|
||||
*/
|
||||
predicate defaultTaintSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
|
||||
* of `c` at sinks and inputs to additional taint steps.
|
||||
@@ -68,6 +62,9 @@ private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
|
||||
/**
|
||||
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
|
||||
* in all global taint flow configurations.
|
||||
@@ -119,7 +116,7 @@ private module Cached {
|
||||
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)
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,20 +116,30 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) { none() }
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,20 +116,30 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) { none() }
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
|
||||
432
ruby/ql/lib/codeql/ruby/experimental/Rbi.qll
Normal file
432
ruby/ql/lib/codeql/ruby/experimental/Rbi.qll
Normal file
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* Provides classes and predicates for working with Ruby Interface (RBI) files
|
||||
* and concepts. RBI files are valid Ruby files that can contain type
|
||||
* information used by Sorbet for typechecking.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with Ruby Interface (RBI) files
|
||||
* and concepts. RBI files are valid Ruby files that can contain type
|
||||
* information used by Sorbet for typechecking.
|
||||
*/
|
||||
module Rbi {
|
||||
/**
|
||||
* Contains classes representing RBI types.
|
||||
*/
|
||||
private module RbiTypes {
|
||||
/**
|
||||
* A node representing a Ruby Interface (RBI) type.
|
||||
*/
|
||||
abstract class RbiType extends Expr { }
|
||||
|
||||
/**
|
||||
* A `ConstantReadAccess` as an RBI type. This is typically a reference to a
|
||||
* class or a constant representing a type alias - for example, the read
|
||||
* accesses to `MyList1`, `Integer` and `MyList2` in:
|
||||
* ```rb
|
||||
* class MyList1; end
|
||||
* MyList2 = T.type_alias(MyList1)
|
||||
* sig { params(l: MyList2).returns(Integer) }
|
||||
* def len(l); end
|
||||
* ```
|
||||
*/
|
||||
class ConstantReadAccessAsRbiType extends RbiType, ConstantReadAccess { }
|
||||
|
||||
/** A method call where the receiver is `T`. */
|
||||
private class MethodCallAgainstT extends MethodCall {
|
||||
MethodCallAgainstT() { this.getReceiver().(ConstantReadAccess).getName() = "T" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.any` - a method that takes `RbiType` parameters, and returns
|
||||
* a type representing the union of those types.
|
||||
*/
|
||||
class RbiUnionType extends RbiType, MethodCallAgainstT {
|
||||
RbiUnionType() { this.getMethodName() = "any" }
|
||||
|
||||
/**
|
||||
* Gets a constituent type of this type union.
|
||||
*/
|
||||
RbiType getAType() { result = this.getArgument(_) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.untyped`.
|
||||
*/
|
||||
class RbiUntypedType extends RbiType, MethodCallAgainstT {
|
||||
RbiUntypedType() { this.getMethodName() = "untyped" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.nilable`, creating a nilable version of the type provided as
|
||||
* an argument.
|
||||
*/
|
||||
class RbiNilableType extends RbiType, MethodCallAgainstT {
|
||||
RbiNilableType() { this.getMethodName() = "nilable" }
|
||||
|
||||
/** Gets the type that this may represent if not nil. */
|
||||
RbiType getUnderlyingType() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.type_alias`. The return value of this call can be assigned to
|
||||
* create a type alias.
|
||||
*/
|
||||
class RbiTypeAlias extends RbiType, MethodCallAgainstT {
|
||||
RbiTypeAlias() { this.getMethodName() = "type_alias" }
|
||||
|
||||
/**
|
||||
* Gets the type aliased by this call.
|
||||
*/
|
||||
RbiType getAliasedType() {
|
||||
exists(ExprNodes::MethodCallCfgNode n | n.getExpr() = this |
|
||||
result = n.getBlock().(ExprNodes::StmtSequenceCfgNode).getLastStmt().getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.self_type`.
|
||||
*/
|
||||
class RbiSelfType extends RbiType, MethodCallAgainstT {
|
||||
RbiSelfType() { this.getMethodName() = "self_type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.noreturn`.
|
||||
*/
|
||||
class RbiNoreturnType extends RbiType, MethodCallAgainstT {
|
||||
RbiNoreturnType() { this.getMethodName() = "noreturn" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ConstantReadAccess` where the constant is from the `T` module.
|
||||
*/
|
||||
private class ConstantReadAccessFromT extends ConstantReadAccess {
|
||||
ConstantReadAccessFromT() { this.getScopeExpr().(ConstantReadAccess).getName() = "T" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A use of `T::Boolean`.
|
||||
*/
|
||||
class RbiBooleanType extends RbiType, ConstantReadAccessFromT {
|
||||
RbiBooleanType() { this.getName() = "Boolean" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A use of `T::Array`.
|
||||
*/
|
||||
class RbiArrayType extends RbiType, ConstantReadAccessFromT {
|
||||
RbiArrayType() { this.getName() = "Array" }
|
||||
|
||||
/** Gets the type of elements of this array. */
|
||||
RbiType getElementType() {
|
||||
exists(ElementReference refNode | refNode.getReceiver() = this |
|
||||
result = refNode.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RbiHashType extends RbiType, ConstantReadAccessFromT {
|
||||
RbiHashType() { this.getName() = "Hash" }
|
||||
|
||||
private ElementReference getRefNode() { result.getReceiver() = this }
|
||||
|
||||
/** Gets the type of keys of this hash type. */
|
||||
Expr getKeyType() { result = this.getRefNode().getArgument(0) }
|
||||
|
||||
/** Gets the type of values of this hash type. */
|
||||
Expr getValueType() { result = this.getRefNode().getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `T.proc`. This defines a type signature for a proc or block
|
||||
*/
|
||||
class ProcCall extends RbiType, SignatureCall, MethodCallAgainstT {
|
||||
ProcCall() { this.getMethodName() = "proc" }
|
||||
|
||||
private ProcReturnsTypeCall getReturnsTypeCall() { result.getProcCall() = this }
|
||||
|
||||
private ProcParamsCall getParamsCall() { result.getProcCall() = this }
|
||||
|
||||
/**
|
||||
* Gets the return type of this type signature.
|
||||
*/
|
||||
override ReturnType getReturnType() { result = this.getReturnsTypeCall().getReturnType() }
|
||||
|
||||
/**
|
||||
* Gets the type of a parameter of this type signature.
|
||||
*/
|
||||
override ParameterType getAParameterType() {
|
||||
result = this.getParamsCall().getAParameterType()
|
||||
}
|
||||
// TODO: get associated method to which this can be passed
|
||||
}
|
||||
}
|
||||
|
||||
import RbiTypes
|
||||
|
||||
/**
|
||||
* A Ruby Interface (RBI) File. These are valid Ruby files that can contain
|
||||
* type information used by Sorbet for typechecking.
|
||||
*
|
||||
* RBI files can contain project source code, or act as external type
|
||||
* definition files for existing Ruby code, which may include code in gems.
|
||||
*/
|
||||
class RbiFile extends File {
|
||||
RbiFile() { this.getExtension() = "rbi" }
|
||||
}
|
||||
|
||||
private newtype TReturnType =
|
||||
TRbiType(RbiType t) { exists(ReturnsCall r | r.getRbiType() = t) } or
|
||||
TVoidType()
|
||||
|
||||
/** A return type of a method. */
|
||||
class ReturnType extends TReturnType {
|
||||
/** Gets a textual representation of this node. */
|
||||
cached
|
||||
string toString() {
|
||||
result = this.getRbiType().toString()
|
||||
or
|
||||
this.isVoidType() and result = "(void)"
|
||||
}
|
||||
|
||||
/** Gets the underlying RbiType, if any. */
|
||||
RbiType getRbiType() { exists(RbiType t | this = TRbiType(t) | result = t) }
|
||||
|
||||
/** Holds if this is the void type. */
|
||||
predicate isVoidType() { this = TVoidType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that defines a type signature for a method or proc.
|
||||
*/
|
||||
abstract class SignatureCall extends MethodCall {
|
||||
/**
|
||||
* Gets the return type of this type signature.
|
||||
*/
|
||||
abstract ReturnType getReturnType();
|
||||
|
||||
/**
|
||||
* Gets the type of a parameter of this type signature.
|
||||
*/
|
||||
abstract ParameterType getAParameterType();
|
||||
}
|
||||
|
||||
private predicate isMethodSignatureCallNode(CfgNode n) {
|
||||
n.(ExprCfgNode).getExpr() instanceof MethodSignatureCall
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is the `i`th transitive successor node of `sigNode` where there
|
||||
* are no intervening nodes corresponding to `MethodSignatureCall`s.
|
||||
*/
|
||||
private predicate methodSignatureSuccessorNodeRanked(CfgNode sigNode, CfgNode n, int i) {
|
||||
// direct successor
|
||||
i = 1 and
|
||||
n = sigNode.getASuccessor() and
|
||||
not isMethodSignatureCallNode(n)
|
||||
or
|
||||
// transitive successor
|
||||
i > 1 and
|
||||
exists(CfgNode np | n = np.getASuccessor() |
|
||||
methodSignatureSuccessorNodeRanked(sigNode, np, i - 1) and
|
||||
not isMethodSignatureCallNode(np)
|
||||
)
|
||||
}
|
||||
|
||||
/** A call to `sig` to define the type signature of a method. */
|
||||
class MethodSignatureCall extends SignatureCall {
|
||||
MethodSignatureCall() { this.getMethodName() = "sig" }
|
||||
|
||||
private MethodReturnsTypeCall getReturnsTypeCall() { result.getMethodSignatureCall() = this }
|
||||
|
||||
private MethodParamsCall getParamsCall() { result.getMethodSignatureCall() = this }
|
||||
|
||||
private ExprCfgNode getCfgNode() { result.getExpr() = this }
|
||||
|
||||
/**
|
||||
* Gets the method whose type signature is defined by this call.
|
||||
*/
|
||||
MethodBase getAssociatedMethod() {
|
||||
result =
|
||||
min(ExprCfgNode methodCfgNode, int i |
|
||||
methodSignatureSuccessorNodeRanked(this.getCfgNode(), methodCfgNode, i) and
|
||||
methodCfgNode.getExpr() instanceof MethodBase
|
||||
|
|
||||
methodCfgNode order by i
|
||||
).getExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `attr_reader` or `attr_accessor` where the return type of
|
||||
* the generated method is described by this call.
|
||||
*/
|
||||
MethodCall getAssociatedAttrReaderCall() {
|
||||
result =
|
||||
min(ExprNodes::MethodCallCfgNode c, int i |
|
||||
c.getExpr().getMethodName() = ["attr_reader", "attr_accessor"] and
|
||||
methodSignatureSuccessorNodeRanked(this.getCfgNode(), c, i)
|
||||
|
|
||||
c order by i
|
||||
).getExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the return type of this type signature.
|
||||
*/
|
||||
override ReturnType getReturnType() { result = this.getReturnsTypeCall().getReturnType() }
|
||||
|
||||
/**
|
||||
* Gets the type of a parameter of this type signature.
|
||||
*/
|
||||
override ParameterType getAParameterType() { result = this.getParamsCall().getAParameterType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call that defines either:
|
||||
* - the parameters to, or
|
||||
* - the return type of
|
||||
* a method.
|
||||
*/
|
||||
class MethodSignatureDefiningCall extends MethodCall {
|
||||
private MethodSignatureCall sigCall;
|
||||
|
||||
MethodSignatureDefiningCall() {
|
||||
exists(MethodCall c | c = sigCall.getBlock().getAChild() |
|
||||
// The typical pattern for the contents of a `sig` block is something
|
||||
// like `params(<param defs>).returns(<return type>)` - we want to
|
||||
// pick up both of these calls.
|
||||
this = c.getReceiver*()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the enclosing `sig` call that defines the overall type signature
|
||||
* for the method associated with this call.
|
||||
*/
|
||||
MethodSignatureCall getMethodSignatureCall() { result = sigCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `params`. This defines the types of parameters to a method or proc.
|
||||
*/
|
||||
class ParamsCall extends MethodCall {
|
||||
ParamsCall() { this.getMethodName() = "params" }
|
||||
|
||||
/**
|
||||
* Gets the type of a parameter defined by this call.
|
||||
*/
|
||||
ParameterType getAParameterType() { result = this.getArgument(_) }
|
||||
}
|
||||
|
||||
abstract class ReturnsTypeCall extends MethodCall {
|
||||
abstract ReturnType getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `returns`. Defines the return type of a method or proc.
|
||||
*/
|
||||
class ReturnsCall extends MethodCall {
|
||||
ReturnsCall() { this.getMethodName() = "returns" }
|
||||
|
||||
/**
|
||||
* Gets the `RbiType` return type of this call.
|
||||
*/
|
||||
RbiType getRbiType() { result = this.getArgument(0) }
|
||||
|
||||
/**
|
||||
* Gets the wrapped `ReturnType` of this call.
|
||||
*/
|
||||
ReturnType getReturnType() { result.getRbiType() = this.getRbiType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `void`. Essentially a "don't-care" for the return type of a method or proc.
|
||||
*/
|
||||
class VoidCall extends MethodCall {
|
||||
VoidCall() { this.getMethodName() = "void" }
|
||||
|
||||
/**
|
||||
* Gets the wrapped `ReturnType` of this call.
|
||||
*/
|
||||
ReturnType getReturnType() { result.isVoidType() }
|
||||
}
|
||||
|
||||
/** A call that defines the return type of a method. */
|
||||
abstract class MethodReturnsTypeCall extends ReturnsTypeCall, MethodSignatureDefiningCall { }
|
||||
|
||||
/** A call to `params` that defines the parameter types of a method */
|
||||
class MethodParamsCall extends ParamsCall, MethodSignatureDefiningCall { }
|
||||
|
||||
/** A call to `returns` that defines the return type of a method. */
|
||||
class MethodReturnsCall extends MethodReturnsTypeCall instanceof ReturnsCall {
|
||||
override ReturnType getReturnType() { result = ReturnsCall.super.getReturnType() }
|
||||
}
|
||||
|
||||
/** A call to `void` that spcifies that a given method does not return a useful value. */
|
||||
class MethodVoidCall extends MethodReturnsTypeCall instanceof VoidCall {
|
||||
override ReturnType getReturnType() { result = VoidCall.super.getReturnType() }
|
||||
}
|
||||
|
||||
/** A call that defines part of the type signature of a proc or block argument. */
|
||||
class ProcSignatureDefiningCall extends MethodCall, RbiType {
|
||||
private ProcCall procCall;
|
||||
|
||||
ProcSignatureDefiningCall() { this.getReceiver+() = procCall }
|
||||
|
||||
/**
|
||||
* Gets the `proc` call that defines the complete type signature for the
|
||||
* associated proc or block argument.
|
||||
*/
|
||||
ProcCall getProcCall() { result = procCall }
|
||||
}
|
||||
|
||||
/** A call that defines the return type of a proc or block */
|
||||
abstract class ProcReturnsTypeCall extends ReturnsTypeCall, ProcSignatureDefiningCall { }
|
||||
|
||||
/** A call that defines the parameter types of a proc or block. */
|
||||
class ProcParamsCall extends ParamsCall, ProcSignatureDefiningCall { }
|
||||
|
||||
/** A call that defines the return type of a non-void proc or block. */
|
||||
class ProcReturnsCall extends ProcReturnsTypeCall instanceof ReturnsCall {
|
||||
override ReturnType getReturnType() { result = ReturnsCall.super.getReturnType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `void` that spcifies that a given proc or block does not return
|
||||
* a useful value.
|
||||
*/
|
||||
class ProcVoidCall extends ProcReturnsTypeCall instanceof VoidCall {
|
||||
override ReturnType getReturnType() { result = VoidCall.super.getReturnType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair defining the type of a parameter to a method.
|
||||
*/
|
||||
class ParameterType extends Pair {
|
||||
private RbiType t;
|
||||
|
||||
ParameterType() { t = this.getValue() }
|
||||
|
||||
/** Gets the `RbiType` of this parameter. */
|
||||
RbiType getType() { result = t }
|
||||
|
||||
private SignatureCall getOuterMethodSignatureCall() { this = result.getAParameterType() }
|
||||
|
||||
private MethodBase getAssociatedMethod() {
|
||||
result = this.getOuterMethodSignatureCall().(MethodSignatureCall).getAssociatedMethod()
|
||||
}
|
||||
|
||||
/** Gets the parameter to which this type applies. */
|
||||
NamedParameter getParameter() {
|
||||
result = this.getAssociatedMethod().getAParameter() and
|
||||
result.getName() = this.getKey().getConstantValue().getStringlikeValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ module ActionDispatch {
|
||||
* ```rb
|
||||
* get "/photos", to: "photos#index"
|
||||
* ```
|
||||
* or via a convenience method like `resources`, which defines mutiple routes at once:
|
||||
* or via a convenience method like `resources`, which defines multiple routes at once:
|
||||
* ```rb
|
||||
* resources :photos
|
||||
* ```
|
||||
|
||||
@@ -240,7 +240,7 @@ abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range,
|
||||
// Names of class methods on ActiveRecord models that may return one or more
|
||||
// instances of that model. This also includes the `initialize` method.
|
||||
// See https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html
|
||||
private string finderMethodName() {
|
||||
private string staticFinderMethodName() {
|
||||
exists(string baseName |
|
||||
baseName =
|
||||
[
|
||||
@@ -275,8 +275,24 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
|
||||
exists(MethodCall call, Expr recv |
|
||||
call = this.asExpr().getExpr() and
|
||||
recv = getUltimateReceiver(call) and
|
||||
resolveConstant(recv) = cls.getAQualifiedName() and
|
||||
call.getMethodName() = finderMethodName()
|
||||
(
|
||||
// The receiver refers to an `ActiveRecordModelClass` by name
|
||||
resolveConstant(recv) = cls.getAQualifiedName()
|
||||
or
|
||||
// The receiver is self, and the call is within a singleton method of
|
||||
// the `ActiveRecordModelClass`
|
||||
recv instanceof SelfVariableAccess and
|
||||
exists(SingletonMethod callScope |
|
||||
callScope = call.getCfgScope() and
|
||||
callScope = cls.getAMethod()
|
||||
)
|
||||
) and
|
||||
(
|
||||
call.getMethodName() = staticFinderMethodName()
|
||||
or
|
||||
// dynamically generated finder methods
|
||||
call.getMethodName().indexOf("find_by_") = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -293,18 +309,24 @@ private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInsta
|
||||
m = this.getCfgScope() and
|
||||
m.getEnclosingModule() = cls and
|
||||
m = cls.getAMethod()
|
||||
)
|
||||
) and
|
||||
// In a singleton method, `self` refers to the class itself rather than an
|
||||
// instance of that class
|
||||
not this.getSelfScope() instanceof SingletonMethod
|
||||
}
|
||||
|
||||
final override ActiveRecordModelClass getClass() { result = cls }
|
||||
}
|
||||
|
||||
// A (locally tracked) active record model object
|
||||
private class ActiveRecordInstance extends DataFlow::Node {
|
||||
/**
|
||||
* An instance of an `ActiveRecord` model object.
|
||||
*/
|
||||
class ActiveRecordInstance extends DataFlow::Node {
|
||||
private ActiveRecordModelInstantiation instantiation;
|
||||
|
||||
ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) }
|
||||
|
||||
/** Gets the `ActiveRecordModelClass` that this is an instance of. */
|
||||
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
|
||||
|
||||
/**
|
||||
* Modeling for `ActiveSupport`.
|
||||
@@ -32,6 +36,107 @@ module ActiveSupport {
|
||||
|
||||
override DataFlow::Node getCode() { result = this.getReceiver() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for methods which transform the receiver in some way, possibly preserving taint.
|
||||
*/
|
||||
private class StringTransformSummary extends SummarizedCallable {
|
||||
// We're modelling a lot of different methods, so we make up a name for this summary.
|
||||
StringTransformSummary() { this = "ActiveSupportStringTransform" }
|
||||
|
||||
override MethodCall getACall() {
|
||||
result.getMethodName() =
|
||||
[
|
||||
"camelize", "camelcase", "classify", "dasherize", "deconstantize", "demodulize",
|
||||
"foreign_key", "humanize", "indent", "parameterize", "pluralize", "singularize",
|
||||
"squish", "strip_heredoc", "tableize", "titlecase", "titleize", "underscore",
|
||||
"upcase_first"
|
||||
]
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extensions to the `Enumerable` module.
|
||||
*/
|
||||
module Enumerable {
|
||||
private class ArrayIndex extends int {
|
||||
ArrayIndex() { this = any(DataFlow::Content::KnownElementContent c).getIndex().getInt() }
|
||||
}
|
||||
|
||||
private class CompactBlankSummary extends SimpleSummarizedCallable {
|
||||
CompactBlankSummary() { this = "compact_blank" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class ExcludingSummary extends SimpleSummarizedCallable {
|
||||
ExcludingSummary() { this = ["excluding", "without"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class InOrderOfSummary extends SimpleSummarizedCallable {
|
||||
InOrderOfSummary() { this = "in_order_of" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Array#push` but doesn't update the receiver.
|
||||
*/
|
||||
private class IncludingSummary extends SimpleSummarizedCallable {
|
||||
IncludingSummary() { this = "including" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
exists(ArrayIndex i |
|
||||
input = "Argument[self].Element[" + i + "]" and
|
||||
output = "ReturnValue.Element[" + i + "]"
|
||||
)
|
||||
or
|
||||
input = "Argument[self].Element[?]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
or
|
||||
exists(int i | i in [0 .. (mc.getNumberOfArguments() - 1)] |
|
||||
input = "Argument[" + i + "]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
)
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
// TODO: index_by, index_with, pick, pluck (they require Hash dataflow)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `ActiveSupport::Logger`
|
||||
*/
|
||||
module Logger {
|
||||
private class ActiveSupportLoggerInstantiation extends StdlibLogger::LoggerInstantiation {
|
||||
ActiveSupportLoggerInstantiation() {
|
||||
this =
|
||||
API::getTopLevelMember("ActiveSupport")
|
||||
.getMember(["Logger", "TaggedLogging"])
|
||||
.getAnInstantiation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
ruby/ql/lib/codeql/ruby/frameworks/Archive.qll
Normal file
33
ruby/ql/lib/codeql/ruby/frameworks/Archive.qll
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Provides classes for working with archive libraries.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Classes and predicates for modeling the RubyZip library
|
||||
*/
|
||||
module RubyZip {
|
||||
/**
|
||||
* A call to `Zip::File.new`, considered as a `FileSystemAccess`
|
||||
*/
|
||||
class RubyZipFileNew extends DataFlow::CallNode, FileSystemAccess::Range {
|
||||
RubyZipFileNew() { this = API::getTopLevelMember("Zip").getMember("File").getAnInstantiation() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Zip::File.open`, considered as a `FileSystemAccess`.
|
||||
*/
|
||||
class RubyZipFileOpen extends DataFlow::CallNode, FileSystemAccess::Range {
|
||||
RubyZipFileOpen() {
|
||||
this = API::getTopLevelMember("Zip").getMember("File").getAMethodCall("open")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import core.Object::Object
|
||||
import core.Kernel::Kernel
|
||||
import core.Module
|
||||
import core.Array
|
||||
import core.Hash
|
||||
import core.String
|
||||
import core.Regexp
|
||||
import core.IO
|
||||
@@ -72,3 +73,15 @@ private class SplatSummary extends SummarizedCallable {
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class HashSplatSummary extends SummarizedCallable {
|
||||
HashSplatSummary() { this = "**(hash-splat)" }
|
||||
|
||||
override HashSplatExpr getACall() { any() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
80
ruby/ql/lib/codeql/ruby/frameworks/PosixSpawn.qll
Normal file
80
ruby/ql/lib/codeql/ruby/frameworks/PosixSpawn.qll
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Provides modeling for the `posix-spawn` gem.
|
||||
* Version: 0.3.15
|
||||
*/
|
||||
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
|
||||
/**
|
||||
* Provides modeling for the `posix-spawn` gem.
|
||||
* Version: 0.3.15
|
||||
*/
|
||||
module PosixSpawn {
|
||||
private API::Node posixSpawnModule() {
|
||||
result = API::getTopLevelMember("POSIX").getMember("Spawn")
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `POSIX::Spawn::Child.new` or `POSIX::Spawn::Child.build`.
|
||||
*/
|
||||
class ChildCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
ChildCall() {
|
||||
this =
|
||||
[
|
||||
posixSpawnModule().getMember("Child").getAMethodCall("build"),
|
||||
posixSpawnModule().getMember("Child").getAnInstantiation()
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() {
|
||||
result = this.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `POSIX::Spawn.spawn` or a related method.
|
||||
*/
|
||||
class SystemCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
SystemCall() {
|
||||
this =
|
||||
posixSpawnModule()
|
||||
.getAMethodCall(["spawn", "fspawn", "popen4", "pspawn", "system", "_pspawn", "`"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { this.argument(result) }
|
||||
|
||||
// From the docs:
|
||||
// When only command is given and includes a space character, the command
|
||||
// text is executed by the system shell interpreter.
|
||||
// This means the following signatures are shell interpreted:
|
||||
//
|
||||
// spawn(cmd)
|
||||
// spawn(cmd, opts)
|
||||
// spawn(env, cmd)
|
||||
// spawn(env, cmd, opts)
|
||||
//
|
||||
// env and opts will be hashes. We over-approximate by assuming the argument
|
||||
// is shell interpreted unless there is another argument with a string
|
||||
// constant value.
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
not exists(DataFlow::Node otherArg |
|
||||
otherArg != arg and
|
||||
this.argument(arg) and
|
||||
this.argument(otherArg) and
|
||||
otherArg.asExpr().getConstantValue().isString(_)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate argument(DataFlow::Node arg) {
|
||||
arg = this.getArgument(_) and
|
||||
not arg.asExpr() instanceof ExprNodes::HashLiteralCfgNode and
|
||||
not arg.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
|
||||
not arg.asExpr() instanceof ExprNodes::PairCfgNode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,17 @@ private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
|
||||
/** An array index that may be tracked precisely in data flow. */
|
||||
private class ArrayIndex extends int {
|
||||
ArrayIndex() { this = any(DataFlow::Content::KnownElementContent c).getIndex().getInt() }
|
||||
}
|
||||
|
||||
private string lastBlockParam(MethodCall mc, string name, int lastBlockParam) {
|
||||
mc.getMethodName() = name and
|
||||
result = name + "(" + lastBlockParam + ")" and
|
||||
lastBlockParam = mc.getBlock().getNumberOfParameters() - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides flow summaries for the `Array` class.
|
||||
*
|
||||
@@ -19,23 +26,9 @@ private class ArrayIndex extends int {
|
||||
* module instead.
|
||||
*/
|
||||
module Array {
|
||||
/**
|
||||
* Gets the constant value of `arg`, which corresponds to a valid known
|
||||
* element index. Unlike calling simply `arg.getConstantValue()`, this
|
||||
* excludes negative array indices.
|
||||
*/
|
||||
bindingset[arg]
|
||||
private ConstantValue getKnownElementIndex(Expr arg) {
|
||||
result =
|
||||
DataFlow::Content::getElementContent(arg.getConstantValue())
|
||||
.(DataFlow::Content::KnownElementContent)
|
||||
.getIndex()
|
||||
}
|
||||
|
||||
bindingset[arg]
|
||||
private predicate isUnknownElementIndex(Expr arg) {
|
||||
not exists(getKnownElementIndex(arg)) and
|
||||
not arg instanceof RangeLiteral
|
||||
private predicate isUnknownElementIndex(Expr e) {
|
||||
not exists(DataFlow::Content::getKnownElementIndex(e)) and
|
||||
not e instanceof RangeLiteral
|
||||
}
|
||||
|
||||
private class ArrayLiteralSummary extends SummarizedCallable {
|
||||
@@ -190,16 +183,17 @@ module Array {
|
||||
|
||||
/** A call to `[]` with a known index. */
|
||||
private class ElementReferenceReadKnownSummary extends ElementReferenceReadSummary {
|
||||
private ConstantValue cv;
|
||||
private ConstantValue index;
|
||||
|
||||
ElementReferenceReadKnownSummary() {
|
||||
this = methodName + "(" + cv.serialize() + ")" and
|
||||
this = methodName + "(" + index.serialize() + ")" and
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
cv = getKnownElementIndex(mc.getArgument(0))
|
||||
index = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) and
|
||||
if methodName = "slice" then index.isInt(_) else any()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[" + [cv.serialize(), "?"] + "]" and
|
||||
input = "Argument[self].Element[?," + index.serialize() + "]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -230,7 +224,7 @@ module Array {
|
||||
|
||||
ElementReferenceRangeReadKnownSummary() {
|
||||
mc.getNumberOfArguments() = 2 and
|
||||
start = getKnownElementIndex(mc.getArgument(0)).getInt() and
|
||||
start = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt() and
|
||||
exists(int length | mc.getArgument(1).getConstantValue().isInt(length) |
|
||||
end = (start + length - 1) and
|
||||
this = "[](" + start + ", " + length + ")"
|
||||
@@ -260,8 +254,8 @@ module Array {
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[self].Element[?]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
input = "Argument[self].WithElement[?]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
exists(ArrayIndex i | i >= start and i <= end |
|
||||
input = "Argument[self].Element[" + i + "]" and
|
||||
@@ -281,8 +275,8 @@ module Array {
|
||||
(
|
||||
mc.getNumberOfArguments() = 2 and
|
||||
(
|
||||
not mc.getArgument(0).getConstantValue().isInt(_) or
|
||||
not mc.getArgument(1).getConstantValue().isInt(_)
|
||||
not exists(mc.getArgument(0).getConstantValue()) or
|
||||
not exists(mc.getArgument(1).getConstantValue())
|
||||
)
|
||||
or
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
@@ -296,7 +290,7 @@ module Array {
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
input = "Argument[self].Element[?,0..]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -314,20 +308,20 @@ module Array {
|
||||
|
||||
/** A call to `[]=` with a known index. */
|
||||
private class ElementReferenceStoreKnownSummary extends ElementReferenceStoreSummary {
|
||||
private ConstantValue cv;
|
||||
private ConstantValue index;
|
||||
|
||||
ElementReferenceStoreKnownSummary() {
|
||||
mc.getNumberOfArguments() = 2 and
|
||||
cv = getKnownElementIndex(mc.getArgument(0)) and
|
||||
this = "[" + cv.serialize() + "]="
|
||||
index = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) and
|
||||
this = "[" + index.serialize() + "]="
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[self].Element[" + cv.serialize() + "]" and
|
||||
output = "Argument[self].Element[" + index.serialize() + "]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[self].WithoutElement[" + cv.serialize() + "]" and
|
||||
input = "Argument[self].WithoutElement[" + index.serialize() + "]" and
|
||||
output = "Argument[self]" and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -382,8 +376,8 @@ module Array {
|
||||
AssocSummary() { this = ["assoc", "rassoc"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
input = "Argument[self].Element[any].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -398,16 +392,16 @@ module Array {
|
||||
}
|
||||
|
||||
private class AtKnownSummary extends AtSummary {
|
||||
private ConstantValue cv;
|
||||
private ConstantValue index;
|
||||
|
||||
AtKnownSummary() {
|
||||
this = "at(" + cv.serialize() + "]" and
|
||||
this = "at(" + index.serialize() + "]" and
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
cv = getKnownElementIndex(mc.getArgument(0))
|
||||
index = DataFlow::Content::getKnownElementIndex(mc.getArgument(0))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[" + [cv.serialize(), "?"] + "]" and
|
||||
input = "Argument[self].Element[" + index.serialize() + ",?]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -490,7 +484,7 @@ module Array {
|
||||
CompactBangSummary() { this = "compact!" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
input = "Argument[self].Element[?,0..]" and
|
||||
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"] and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -519,19 +513,82 @@ module Array {
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteSummary extends SimpleSummarizedCallable {
|
||||
DeleteSummary() { this = "delete" }
|
||||
abstract private class DeleteSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
bindingset[this]
|
||||
DeleteSummary() { mc.getMethodName() = "delete" }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = ["Argument[self].Element[?]", "ReturnValue"]
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]"
|
||||
or
|
||||
input = "Argument[self].WithElement[?]" and
|
||||
output = "Argument[self]"
|
||||
or
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteKnownSummary extends DeleteSummary {
|
||||
private ConstantValue index;
|
||||
|
||||
DeleteKnownSummary() {
|
||||
this = "delete(" + index.serialize() + ")" and
|
||||
mc.getArgument(0).getConstantValue() = index
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
(
|
||||
(
|
||||
if index.isInt(_)
|
||||
then
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].WithoutElement[" + index.serialize() + "].Element[0..]" and
|
||||
output = "Argument[self].Element[?]"
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..]" and
|
||||
output = "Argument[self]"
|
||||
else (
|
||||
input = "Argument[self].WithoutElement[" + index.serialize() + "]" and
|
||||
output = "Argument[self]"
|
||||
)
|
||||
)
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
input = "Argument[self].Element[" + index.serialize() + ",?]" and
|
||||
output = "ReturnValue"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteUnknownSummary extends DeleteSummary {
|
||||
DeleteUnknownSummary() {
|
||||
this = "delete" and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
(
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = "Argument[self].Element[?]"
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = "Argument[self]"
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -603,17 +660,26 @@ module Array {
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteIfSummary extends SimpleSummarizedCallable {
|
||||
DeleteIfSummary() { this = "delete_if" }
|
||||
private class DeleteIfSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
DeleteIfSummary() { this = lastBlockParam(mc, "delete_if", lastBlockParam) }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output =
|
||||
["Argument[block].Parameter[0]", "ReturnValue.Element[?]", "Argument[self].Element[?]"] and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]" and
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
or
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = ["ReturnValue", "Argument[self]"]
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -631,9 +697,9 @@ module Array {
|
||||
private string getDigArg(MethodCall dig, int i) {
|
||||
dig.getMethodName() = "dig" and
|
||||
exists(Expr arg | arg = dig.getArgument(i) |
|
||||
result = getKnownElementIndex(arg).(ConstantValue::ConstantIntegerValue).serialize()
|
||||
result = DataFlow::Content::getKnownElementIndex(arg).serialize()
|
||||
or
|
||||
not getKnownElementIndex(arg).isInt(_) and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(arg)) and
|
||||
result = "?"
|
||||
)
|
||||
}
|
||||
@@ -647,7 +713,7 @@ module Array {
|
||||
private string buildDigInputSpecComponent(RelevantDigMethodCall dig, int i) {
|
||||
exists(string s |
|
||||
s = getDigArg(dig, i) and
|
||||
if s = "?" then result = "any" else result = [s, "?"]
|
||||
if s = "?" then result = "any" else result = s + ",?"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -683,14 +749,24 @@ module Array {
|
||||
}
|
||||
}
|
||||
|
||||
private class EachSummary extends SimpleSummarizedCallable {
|
||||
// `each` and `reverse_each` are the same in terms of flow inputs/outputs.
|
||||
EachSummary() { this = ["each", "reverse_each"] }
|
||||
private class EachSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
EachSummary() {
|
||||
exists(string name |
|
||||
// `each` and `reverse_each` are the same in terms of flow inputs/outputs.
|
||||
name = ["each", "reverse_each"] and
|
||||
this = lastBlockParam(mc, name, lastBlockParam)
|
||||
)
|
||||
}
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[0]"
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
or
|
||||
input = "Argument[self].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
@@ -700,7 +776,7 @@ module Array {
|
||||
}
|
||||
|
||||
private class EachIndexSummary extends SimpleSummarizedCallable {
|
||||
EachIndexSummary() { this = "each_index" }
|
||||
EachIndexSummary() { this = ["each_index", "each_key"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithElement[any]" and
|
||||
@@ -719,17 +795,17 @@ module Array {
|
||||
}
|
||||
|
||||
private class FetchKnownSummary extends FetchSummary {
|
||||
int i;
|
||||
ConstantValue index;
|
||||
|
||||
FetchKnownSummary() {
|
||||
this = "fetch(" + i + ")" and
|
||||
mc.getArgument(0).getConstantValue().isInt(i) and
|
||||
i >= 0
|
||||
this = "fetch(" + index.serialize() + ")" and
|
||||
index = mc.getArgument(0).getConstantValue() and
|
||||
not index.isInt(any(int i | i < 0))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[?," + i + "]" and
|
||||
input = "Argument[self].Element[?," + index.serialize() + "]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[0]" and
|
||||
@@ -745,7 +821,9 @@ module Array {
|
||||
private class FetchUnknownSummary extends FetchSummary {
|
||||
FetchUnknownSummary() {
|
||||
this = "fetch(index)" and
|
||||
not exists(int i | mc.getArgument(0).getConstantValue().isInt(i) and i >= 0)
|
||||
not exists(ConstantValue index |
|
||||
index = mc.getArgument(0).getConstantValue() and not index.isInt(any(int i | i < 0))
|
||||
)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
@@ -936,17 +1014,29 @@ module Array {
|
||||
override MethodCall getACall() { result = mc }
|
||||
}
|
||||
|
||||
private class KeepIfSummary extends SimpleSummarizedCallable {
|
||||
KeepIfSummary() { this = "keep_if" }
|
||||
private class KeepIfSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
KeepIfSummary() { this = lastBlockParam(mc, "keep_if", lastBlockParam) }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output =
|
||||
["ReturnValue.Element[?]", "Argument[self].Element[?]", "Argument[block].Parameter[0]"] and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]" and
|
||||
(
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]"
|
||||
or
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = ["ReturnValue", "Argument[self]"]
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -1107,17 +1197,26 @@ module Array {
|
||||
}
|
||||
}
|
||||
|
||||
private class RejectBangSummary extends SimpleSummarizedCallable {
|
||||
RejectBangSummary() { this = "reject!" }
|
||||
private class RejectBangSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
RejectBangSummary() { this = lastBlockParam(mc, "reject!", lastBlockParam) }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output =
|
||||
["ReturnValue.Element[?]", "Argument[self].Element[?]", "Argument[block].Parameter[0]"] and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]" and
|
||||
(
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = ["ReturnValue", "Argument[self]"]
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -1169,7 +1268,7 @@ module Array {
|
||||
private int c;
|
||||
|
||||
RotateKnownSummary() {
|
||||
getKnownElementIndex(mc.getArgument(0)).isInt(c) and
|
||||
DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).isInt(c) and
|
||||
this = "rotate(" + c + ")"
|
||||
or
|
||||
not exists(mc.getArgument(0)) and c = 1 and this = "rotate"
|
||||
@@ -1197,7 +1296,7 @@ module Array {
|
||||
RotateUnknownSummary() {
|
||||
this = "rotate(index)" and
|
||||
exists(mc.getArgument(0)) and
|
||||
not getKnownElementIndex(mc.getArgument(0)).isInt(_)
|
||||
not DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).isInt(_)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
@@ -1267,18 +1366,34 @@ module Array {
|
||||
}
|
||||
}
|
||||
|
||||
private class SelectBangSummary extends SimpleSummarizedCallable {
|
||||
// `filter!` is an alias for `select!`
|
||||
SelectBangSummary() { this = ["select!", "filter!"] }
|
||||
private class SelectBangSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
SelectBangSummary() {
|
||||
exists(string name |
|
||||
name = ["select!", "filter!"] and
|
||||
this = lastBlockParam(mc, name, lastBlockParam)
|
||||
)
|
||||
}
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output =
|
||||
["Argument[block].Parameter[0]", "Argument[self].Element[?]", "ReturnValue.Element[?]"] and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]" and
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
or
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = ["ReturnValue", "Argument[self]"]
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -1306,8 +1421,18 @@ module Array {
|
||||
or
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[self].WithoutElement[0..]" and
|
||||
output = "Argument[self]"
|
||||
or
|
||||
input = "Argument[self].Element[?]" and
|
||||
output = ["ReturnValue", "Argument[self].Element[?]"]
|
||||
output =
|
||||
[
|
||||
"ReturnValue", // array
|
||||
"ReturnValue.Element[1]" // hash
|
||||
]
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithoutElement[?].Element[any]" and
|
||||
output = "ReturnValue.Element[1]"
|
||||
or
|
||||
exists(ArrayIndex i | input = "Argument[self].Element[" + i + "]" |
|
||||
i = 0 and output = "ReturnValue"
|
||||
@@ -1403,7 +1528,7 @@ module Array {
|
||||
SliceBangKnownIndexSummary() {
|
||||
this = "slice!(" + n + ")" and
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
n = getKnownElementIndex(mc.getArgument(0)).getInt()
|
||||
n = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
@@ -1461,7 +1586,7 @@ module Array {
|
||||
|
||||
SliceBangRangeKnownSummary() {
|
||||
mc.getNumberOfArguments() = 2 and
|
||||
start = getKnownElementIndex(mc.getArgument(0)).getInt() and
|
||||
start = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt() and
|
||||
exists(int length | mc.getArgument(1).getConstantValue().isInt(length) |
|
||||
end = (start + length - 1) and
|
||||
this = "slice!(" + start + ", " + length + ")"
|
||||
@@ -1633,47 +1758,43 @@ module Array {
|
||||
override Call getACall() { result = mc }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `values_at` where all the arguments are known, positive integers.
|
||||
*/
|
||||
private string getValuesAtComponent(MethodCall mc, int i) {
|
||||
mc.getMethodName() = "values_at" and
|
||||
result = DataFlow::Content::getKnownElementIndex(mc.getArgument(i)).serialize()
|
||||
}
|
||||
|
||||
private class ValuesAtKnownSummary extends ValuesAtSummary {
|
||||
ValuesAtKnownSummary() {
|
||||
this = "values_at(known)" and
|
||||
forall(int i | i in [0 .. mc.getNumberOfArguments() - 1] |
|
||||
getKnownElementIndex(mc.getArgument(i)).isInt(_)
|
||||
)
|
||||
this =
|
||||
"values_at(" +
|
||||
strictconcat(int i |
|
||||
exists(mc.getArgument(i))
|
||||
|
|
||||
getValuesAtComponent(mc, i), "," order by i
|
||||
) + ")"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[self].Element[?]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
or
|
||||
exists(ArrayIndex elementIndex, int argIndex |
|
||||
argIndex in [0 .. mc.getNumberOfArguments() - 1] and
|
||||
getKnownElementIndex(mc.getArgument(argIndex)).isInt(elementIndex)
|
||||
|
|
||||
input = "Argument[self].Element[" + elementIndex + "]" and
|
||||
output = "ReturnValue.Element[" + argIndex + "]"
|
||||
)
|
||||
)
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
exists(string s, int i |
|
||||
s = getValuesAtComponent(mc, i) and
|
||||
input = "Argument[self].Element[" + s + "]" and
|
||||
output = "ReturnValue.Element[" + i + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `values_at` where at least one of the arguments is not a known,
|
||||
* positive integer.
|
||||
*/
|
||||
private class ValuesAtUnknownSummary extends ValuesAtSummary {
|
||||
ValuesAtUnknownSummary() {
|
||||
this = "values_at(unknown)" and
|
||||
exists(int i | i in [0 .. mc.getNumberOfArguments() - 1] |
|
||||
not getKnownElementIndex(mc.getArgument(i)).isInt(_)
|
||||
)
|
||||
exists(int i | exists(mc.getArgument(i)) | not exists(getValuesAtComponent(mc, i))) and
|
||||
this = "values_at(?)"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
@@ -1742,9 +1863,16 @@ module Enumerable {
|
||||
CompactSummary() { this = "compact" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
input = "Argument[self].Element[?,0..]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
or
|
||||
exists(ConstantValue index |
|
||||
not index.isInt(_) and
|
||||
input = "Argument[self].WithElement[" + index.serialize() + "]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2254,33 +2382,75 @@ module Enumerable {
|
||||
}
|
||||
}
|
||||
|
||||
private class QuerySummary extends SimpleSummarizedCallable {
|
||||
QuerySummary() { this = ["all?", "any?", "none?", "one?"] }
|
||||
private class QuerySummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
QuerySummary() {
|
||||
exists(string name |
|
||||
name = ["all?", "any?", "none?", "one?"] and
|
||||
this = lastBlockParam(mc, name, lastBlockParam)
|
||||
)
|
||||
}
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[0]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class RejectSummary extends SimpleSummarizedCallable {
|
||||
RejectSummary() { this = "reject" }
|
||||
private class RejectSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
RejectSummary() { this = lastBlockParam(mc, "reject", lastBlockParam) }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
|
||||
(
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SelectSummary extends SimpleSummarizedCallable {
|
||||
// `find_all` and `filter` are aliases of `select`.
|
||||
SelectSummary() { this = ["select", "find_all", "filter"] }
|
||||
private class SelectSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
int lastBlockParam;
|
||||
|
||||
SelectSummary() {
|
||||
exists(string name |
|
||||
name = ["select", "find_all", "filter"] and
|
||||
this = lastBlockParam(mc, name, lastBlockParam)
|
||||
)
|
||||
}
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
|
||||
(
|
||||
// array indices may get shifted
|
||||
input = "Argument[self].Element[0..]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
or
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[" + lastBlockParam + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -2406,7 +2576,7 @@ module Enumerable {
|
||||
ToASummary() { this = ["to_a", "entries", "to_ary"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self]" and
|
||||
input = "Argument[self].WithElement[?,0..]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
|
||||
570
ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll
Normal file
570
ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll
Normal file
@@ -0,0 +1,570 @@
|
||||
/** Provides flow summaries for the `Hash` class. */
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
|
||||
/**
|
||||
* Provides flow summaries for the `Hash` class.
|
||||
*
|
||||
* The summaries are ordered (and implemented) based on
|
||||
* https://docs.ruby-lang.org/en/3.1/Hash.html.
|
||||
*
|
||||
* Some summaries are shared with the `Array` class, and those are defined
|
||||
* in `Array.qll`.
|
||||
*/
|
||||
module Hash {
|
||||
// cannot use API graphs due to negative recursion
|
||||
private predicate isHashLiteralPair(Pair pair, ConstantValue key) {
|
||||
key = DataFlow::Content::getKnownElementIndex(pair.getKey()) and
|
||||
pair = any(MethodCall mc | mc.getMethodName() = "[]").getAnArgument()
|
||||
}
|
||||
|
||||
private class HashLiteralSymbolSummary extends SummarizedCallable {
|
||||
private ConstantValue::ConstantSymbolValue symbol;
|
||||
|
||||
HashLiteralSymbolSummary() {
|
||||
isHashLiteralPair(_, symbol) and
|
||||
this = "Hash.[" + symbol.serialize() + "]"
|
||||
}
|
||||
|
||||
final override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
|
||||
exists(result.getKeywordArgument(symbol.getSymbol()))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// { symbol: x }
|
||||
input = "Argument[" + symbol.getSymbol() + ":]" and
|
||||
output = "ReturnValue.Element[" + symbol.serialize() + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class HashLiteralNonSymbolSummary extends SummarizedCallable {
|
||||
private ConstantValue key;
|
||||
|
||||
HashLiteralNonSymbolSummary() {
|
||||
this = "Hash.[]" and
|
||||
isHashLiteralPair(_, key) and
|
||||
not key.isSymbol(_)
|
||||
}
|
||||
|
||||
final override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
|
||||
isHashLiteralPair(result.getAnArgument(), key)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// { 'nonsymbol' => x }
|
||||
input = "Argument[0..].PairValue[" + key.serialize() + "]" and
|
||||
output = "ReturnValue.Element[" + key.serialize() + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class HashLiteralHashSplatSummary extends SummarizedCallable {
|
||||
HashLiteralHashSplatSummary() { this = "Hash.[**]" }
|
||||
|
||||
final override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
|
||||
result.getAnArgument() instanceof HashSplatExpr
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// { **hash }
|
||||
input = "Argument[hash-splat].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `Hash[]` called on an existing hash, e.g.
|
||||
*
|
||||
* ```rb
|
||||
* h = {foo: 0, bar: 1, baz: 2}
|
||||
* Hash[h] # => {:foo=>0, :bar=>1, :baz=>2}
|
||||
* ```
|
||||
*
|
||||
* or on a 2-element array, e.g.
|
||||
*
|
||||
* ```rb
|
||||
* Hash[ [ [:foo, 0], [:bar, 1] ] ] # => {:foo=>0, :bar=>1}
|
||||
* ```
|
||||
*/
|
||||
private class HashNewSummary extends SummarizedCallable {
|
||||
HashNewSummary() { this = "Hash[]" }
|
||||
|
||||
final override ElementReference getACall() {
|
||||
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
|
||||
result.getNumberOfArguments() = 1
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
// Hash[{symbol: x}]
|
||||
input = "Argument[0].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
// Hash[[:symbol, x]]
|
||||
input = "Argument[0].Element[any].Element[1]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `Hash[]` called on an even number of arguments, e.g.
|
||||
*
|
||||
* ```rb
|
||||
* Hash[:foo, 0, :bar, 1] # => {:foo=>0, :bar=>1}
|
||||
* ```
|
||||
*/
|
||||
private class HashNewSuccessivePairsSummary extends SummarizedCallable {
|
||||
private int i;
|
||||
private ConstantValue key;
|
||||
|
||||
HashNewSuccessivePairsSummary() {
|
||||
this = "Hash[" + i + ", " + key.serialize() + "]" and
|
||||
i % 2 = 1 and
|
||||
exists(ElementReference er |
|
||||
key = er.getArgument(i - 1).getConstantValue() and
|
||||
exists(er.getArgument(i))
|
||||
)
|
||||
}
|
||||
|
||||
final override ElementReference getACall() {
|
||||
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
|
||||
key = result.getArgument(i - 1).getConstantValue() and
|
||||
exists(result.getArgument(i))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// Hash[:symbol, x]
|
||||
input = "Argument[" + i + "]" and
|
||||
output = "ReturnValue.Element[" + key.serialize() + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class TryConvertSummary extends SummarizedCallable {
|
||||
TryConvertSummary() { this = "Hash.try_convert" }
|
||||
|
||||
override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("try_convert").getExprNode().getExpr()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class StoreSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
bindingset[this]
|
||||
StoreSummary() { mc.getMethodName() = "store" and mc.getNumberOfArguments() = 2 }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[1]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class StoreKnownSummary extends StoreSummary {
|
||||
private ConstantValue key;
|
||||
|
||||
StoreKnownSummary() {
|
||||
key = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) and
|
||||
this = "store(" + key.serialize() + ")"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[self].Element[" + key.serialize() + "]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[self].WithoutElement[" + key.serialize() + "]" and
|
||||
output = "Argument[self]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class StoreUnknownSummary extends StoreSummary {
|
||||
StoreUnknownSummary() {
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0))) and
|
||||
this = "store"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[self].Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class AssocSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
bindingset[this]
|
||||
AssocSummary() { mc.getMethodName() = "assoc" }
|
||||
|
||||
override MethodCall getACall() { result = mc }
|
||||
}
|
||||
|
||||
private class AssocKnownSummary extends AssocSummary {
|
||||
private ConstantValue key;
|
||||
|
||||
AssocKnownSummary() {
|
||||
this = "assoc(" + key.serialize() + "]" and
|
||||
not key.isInt(_) and // exclude arrays
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
key = DataFlow::Content::getKnownElementIndex(mc.getArgument(0))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[" + key.serialize() + ",?]" and
|
||||
output = "ReturnValue.Element[1]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class AssocUnknownSummary extends AssocSummary {
|
||||
AssocUnknownSummary() {
|
||||
this = "assoc" and
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any].WithoutElement[any]" and
|
||||
output = "ReturnValue.Element[1]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class EachPairSummary extends SimpleSummarizedCallable {
|
||||
EachPairSummary() { this = "each_pair" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[1]"
|
||||
or
|
||||
input = "Argument[self].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class EachValueSummary extends SimpleSummarizedCallable {
|
||||
EachValueSummary() { this = "each_value" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[0]"
|
||||
or
|
||||
input = "Argument[self].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private string getExceptComponent(MethodCall mc, int i) {
|
||||
mc.getMethodName() = "except" and
|
||||
result = DataFlow::Content::getKnownElementIndex(mc.getArgument(i)).serialize()
|
||||
}
|
||||
|
||||
private class ExceptSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
ExceptSummary() {
|
||||
mc.getMethodName() = "except" and
|
||||
this =
|
||||
"except(" + concat(int i, string s | s = getExceptComponent(mc, i) | s, "," order by i) +
|
||||
")"
|
||||
}
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input =
|
||||
"Argument[self]" +
|
||||
concat(int i, string s |
|
||||
s = getExceptComponent(mc, i)
|
||||
|
|
||||
".WithoutElement[" + s + "]" order by i
|
||||
) and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class FetchValuesSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
bindingset[this]
|
||||
FetchValuesSummary() { mc.getMethodName() = "fetch_values" }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].WithElement[?]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[0]" and
|
||||
output = "Argument[block].Parameter[0]"
|
||||
or
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class FetchValuesKnownSummary extends FetchValuesSummary {
|
||||
ConstantValue key;
|
||||
|
||||
FetchValuesKnownSummary() {
|
||||
forex(Expr arg | arg = mc.getAnArgument() | exists(arg.getConstantValue())) and
|
||||
key = mc.getAnArgument().getConstantValue() and
|
||||
this = "fetch_values(" + key.serialize() + ")"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
input = "Argument[self].Element[" + key.serialize() + "]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class FetchValuesUnknownSummary extends FetchValuesSummary {
|
||||
FetchValuesUnknownSummary() {
|
||||
exists(Expr arg | arg = mc.getAnArgument() | not exists(arg.getConstantValue())) and
|
||||
this = "fetch_values(?)"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
super.propagatesFlowExt(input, output, preservesValue)
|
||||
or
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class MergeSummary extends SimpleSummarizedCallable {
|
||||
MergeSummary() { this = "merge" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self,any].WithElement[any]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[self,any].Element[any]" and
|
||||
output = "Argument[block].Parameter[1,2]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class MergeBangSummary extends SimpleSummarizedCallable {
|
||||
MergeBangSummary() { this = ["merge!", "update"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self,any].WithElement[any]" and
|
||||
output = ["ReturnValue", "Argument[self]"]
|
||||
or
|
||||
input = "Argument[self,any].Element[any]" and
|
||||
output = "Argument[block].Parameter[1,2]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class RassocSummary extends SimpleSummarizedCallable {
|
||||
RassocSummary() { this = "rassoc" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any].WithoutElement[any]" and
|
||||
output = "ReturnValue.Element[1]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class SliceSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
bindingset[this]
|
||||
SliceSummary() { mc.getMethodName() = "slice" }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
}
|
||||
|
||||
private class SliceKnownSummary extends SliceSummary {
|
||||
ConstantValue key;
|
||||
|
||||
SliceKnownSummary() {
|
||||
key = mc.getAnArgument().getConstantValue() and
|
||||
this = "slice(" + key.serialize() + ")" and
|
||||
not key.isInt(_) // covered in `Array.qll`
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithElement[?," + key.serialize() + "]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SliceUnknownSummary extends SliceSummary {
|
||||
SliceUnknownSummary() {
|
||||
exists(Expr arg | arg = mc.getAnArgument() | not exists(arg.getConstantValue())) and
|
||||
this = "slice(?)"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class ToASummary extends SimpleSummarizedCallable {
|
||||
ToASummary() { this = "to_a" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithoutElement[0..].Element[any]" and
|
||||
output = "ReturnValue.Element[?].Element[1]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class ToHWithoutBlockSummary extends SimpleSummarizedCallable {
|
||||
ToHWithoutBlockSummary() { this = ["to_h", "to_hash"] and not exists(mc.getBlock()) }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class ToHWithBlockSummary extends SimpleSummarizedCallable {
|
||||
ToHWithBlockSummary() { this = "to_h" and exists(mc.getBlock()) }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[1]"
|
||||
or
|
||||
input = "Argument[block].ReturnValue.Element[1]" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformKeysSummary extends SimpleSummarizedCallable {
|
||||
TransformKeysSummary() { this = "transform_keys" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformKeysBangSummary extends SimpleSummarizedCallable {
|
||||
TransformKeysBangSummary() { this = "transform_keys!" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[self].Element[?]"
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformValuesSummary extends SimpleSummarizedCallable {
|
||||
TransformValuesSummary() { this = "transform_values" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[0]"
|
||||
or
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue.Element[?]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformValuesBangSummary extends SimpleSummarizedCallable {
|
||||
TransformValuesBangSummary() { this = "transform_values!" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[0]"
|
||||
or
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "Argument[self].Element[?]"
|
||||
or
|
||||
input = "Argument[self].WithoutElement[any]" and
|
||||
output = "Argument[self]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class ValuesSummary extends SimpleSummarizedCallable {
|
||||
ValuesSummary() { this = "values" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class ValuesAtSummary extends SummarizedCallable {
|
||||
MethodCall mc;
|
||||
|
||||
bindingset[this]
|
||||
ValuesAtSummary() { mc.getMethodName() = "values_at" }
|
||||
|
||||
final override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].WithElement[?]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@
|
||||
* A `(package,type)` pair may refer to a static type or a synthetic type name used internally in the model.
|
||||
* Synthetic type names can be used to reuse intermediate sub-paths, when there are multiple ways to access the same
|
||||
* element.
|
||||
* See `ModelsAsData.qll` for the langauge-specific interpretation of packages and static type names.
|
||||
* See `ModelsAsData.qll` for the language-specific interpretation of packages and static type names.
|
||||
*
|
||||
* By convention, if one wants to avoid clashes with static types from the package, the type name
|
||||
* should be prefixed with a tilde character (`~`). For example, `(foo, ~Bar)` can be used to indicate that
|
||||
@@ -299,7 +299,7 @@ private class AccessPathRange extends AccessPath::Range {
|
||||
bindingset[token]
|
||||
API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
// API graphs use the same label for arguments and parameters. An edge originating from a
|
||||
// use-node represents be an argument, and an edge originating from a def-node represents a parameter.
|
||||
// use-node represents an argument, and an edge originating from a def-node represents a parameter.
|
||||
// We just map both to the same thing.
|
||||
token.getName() = ["Argument", "Parameter"] and
|
||||
result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument()))
|
||||
@@ -396,7 +396,7 @@ predicate isValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a valid name for an access path token with no arguments, occuring
|
||||
* Holds if `name` is a valid name for an access path token with no arguments, occurring
|
||||
* in an identifying access path.
|
||||
*/
|
||||
bindingset[name]
|
||||
|
||||
@@ -115,7 +115,7 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
or
|
||||
token.getName() = "Parameter" and
|
||||
result =
|
||||
node.getASuccessor(API::Label::getLabelFromArgumentPosition(FlowSummaryImplSpecific::parseParamBody(token
|
||||
node.getASuccessor(API::Label::getLabelFromParameterPosition(FlowSummaryImplSpecific::parseArgBody(token
|
||||
.getAnArgument())))
|
||||
// Note: The "Element" token is not implemented yet, as it ultimately requires type-tracking and
|
||||
// API graphs to be aware of the steps involving Element contributed by the standard library model.
|
||||
@@ -129,7 +129,7 @@ bindingset[token]
|
||||
API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) {
|
||||
token.getName() = "Argument" and
|
||||
result =
|
||||
node.getASuccessor(API::Label::getLabelFromParameterPosition(FlowSummaryImplSpecific::parseArgBody(token
|
||||
node.getASuccessor(API::Label::getLabelFromArgumentPosition(FlowSummaryImplSpecific::parseParamBody(token
|
||||
.getAnArgument())))
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a valid name for an access path token with no arguments, occuring
|
||||
* Holds if `name` is a valid name for an access path token with no arguments, occurring
|
||||
* in an identifying access path.
|
||||
*/
|
||||
predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
@@ -181,7 +181,7 @@ predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string a
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
(
|
||||
argument = ["self", "block"]
|
||||
argument = ["self", "block", "any", "any-named"]
|
||||
or
|
||||
argument.regexpMatch("\\w+:") // keyword argument
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
module Logger {
|
||||
/** A reference to a `Logger` instance */
|
||||
private DataFlow::Node loggerInstance() {
|
||||
result = API::getTopLevelMember("Logger").getAnInstantiation()
|
||||
result instanceof LoggerInstantiation
|
||||
or
|
||||
exists(DataFlow::Node inst |
|
||||
inst = loggerInstance() and
|
||||
@@ -33,11 +33,29 @@ module Logger {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An instantiation of a logger that responds to the std lib logging methods.
|
||||
* This can be extended to recognize additional instances that conform to the
|
||||
* same interface.
|
||||
*/
|
||||
abstract class LoggerInstantiation extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* An instantiation of the std lib `Logger` class.
|
||||
*/
|
||||
private class StdlibLoggerInstantiation extends LoggerInstantiation {
|
||||
StdlibLoggerInstantiation() { this = API::getTopLevelMember("Logger").getAnInstantiation() }
|
||||
}
|
||||
|
||||
private class LoggerInstance extends DataFlow::Node {
|
||||
LoggerInstance() { this = loggerInstance() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a `Logger` instance method that causes a message to be logged.
|
||||
*/
|
||||
abstract class LoggerLoggingCall extends Logging::Range, DataFlow::CallNode {
|
||||
LoggerLoggingCall() { this.getReceiver() = loggerInstance() }
|
||||
LoggerLoggingCall() { this.getReceiver() instanceof LoggerInstance }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
This directory contains QL modules that model classes and modules in the Ruby standard library.
|
||||
Files are named after the most common or top-level class in each library.
|
||||
See https://docs.ruby-lang.org/en/3.1/doc/standard_library_rdoc.html for a full list.
|
||||
See https://docs.ruby-lang.org/en/3.1/standard_library_rdoc.html for a full list.
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.CryptoAlgorithms as CryptoAlgorithms
|
||||
|
||||
@@ -11,3 +11,79 @@
|
||||
*/
|
||||
|
||||
private import ConceptsImports
|
||||
|
||||
/**
|
||||
* Provides models for cryptographic concepts.
|
||||
*
|
||||
* Note: The `CryptographicAlgorithm` class currently doesn't take weak keys into
|
||||
* consideration for the `isWeak` member predicate. So RSA is always considered
|
||||
* secure, although using a low number of bits will actually make it insecure. We plan
|
||||
* to improve our libraries in the future to more precisely capture this aspect.
|
||||
*/
|
||||
module Cryptography {
|
||||
class CryptographicAlgorithm = CryptoAlgorithms::CryptographicAlgorithm;
|
||||
|
||||
class EncryptionAlgorithm = CryptoAlgorithms::EncryptionAlgorithm;
|
||||
|
||||
class HashingAlgorithm = CryptoAlgorithms::HashingAlgorithm;
|
||||
|
||||
class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm;
|
||||
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CryptographicOperation::Range` instead.
|
||||
*/
|
||||
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
|
||||
/**
|
||||
* Gets the block mode used to perform this cryptographic operation.
|
||||
* This may have no result - for example if the `CryptographicAlgorithm` used
|
||||
* is a stream cipher rather than a block cipher.
|
||||
*/
|
||||
BlockMode getBlockMode() { result = super.getBlockMode() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling new applications of a cryptographic algorithms. */
|
||||
module CryptographicOperation {
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CryptographicOperation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
abstract CryptographicAlgorithm getAlgorithm();
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
|
||||
/**
|
||||
* Gets the block mode used to perform this cryptographic operation.
|
||||
* This may have no result - for example if the `CryptographicAlgorithm` used
|
||||
* is a stream cipher rather than a block cipher.
|
||||
*/
|
||||
abstract BlockMode getBlockMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cryptographic block cipher mode of operation. This can be used to encrypt
|
||||
* data of arbitrary length using a block encryption algorithm.
|
||||
*/
|
||||
class BlockMode extends string {
|
||||
BlockMode() { this = ["ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR", "OPENPGP"] }
|
||||
|
||||
/** Holds if this block mode is considered to be insecure. */
|
||||
predicate isWeak() { this = "ECB" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ private module RegexpMatching {
|
||||
* but if `ignorePrefix` is true, it will only match "foo".
|
||||
*/
|
||||
predicate test(string str, boolean ignorePrefix) {
|
||||
none() // maybe overriden in subclasses
|
||||
none() // maybe overridden in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `test(..)`, but where the `fillsCaptureGroup` afterwards tells which capture groups were filled by the given string.
|
||||
*/
|
||||
predicate testWithGroups(string str, boolean ignorePrefix) {
|
||||
none() // maybe overriden in subclasses
|
||||
none() // maybe overridden in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,9 +22,16 @@ module CodeInjection {
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "Code injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "Code injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
|
||||
@@ -20,9 +20,13 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard or
|
||||
guard instanceof StringConstCompare or
|
||||
guard instanceof StringConstArrayInclusionCall
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof Sanitizer or
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,9 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof StringConstCompare or
|
||||
guard instanceof StringConstArrayInclusionCall
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof Sanitizer or
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm
|
||||
override string getName() { result = name }
|
||||
|
||||
override predicate isWeak() { isWeak = true }
|
||||
|
||||
/** Holds if this algorithm is a stream cipher. */
|
||||
predicate isStreamCipher() { isStreamCipher(name) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Provides predicates for reasoning about improper memoization methods.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
|
||||
/**
|
||||
* A `||=` statement that memoizes a value by storing it in an instance variable.
|
||||
*/
|
||||
private class MemoStmt extends AssignLogicalOrExpr {
|
||||
private InstanceVariableAccess instanceVariable;
|
||||
|
||||
MemoStmt() { instanceVariable.getParent*() = this.getLeftOperand() }
|
||||
|
||||
/**
|
||||
* Gets the variable access on the LHS of this statement.
|
||||
* This is the `@a` in `@a ||= e`.
|
||||
*/
|
||||
VariableAccess getVariableAccess() { result = instanceVariable }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `||=` statement that stores a value at a particular location in a Hash,
|
||||
* which itself is stored in an instance variable.
|
||||
* For example:
|
||||
* ```rb
|
||||
* @a[key] ||= e
|
||||
* ```
|
||||
*/
|
||||
private class HashMemoStmt extends MemoStmt {
|
||||
HashMemoStmt() {
|
||||
exists(ElementReference e |
|
||||
e = this.getLeftOperand() and this.getVariableAccess().getParent+() = e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that may be performing memoization.
|
||||
*/
|
||||
private class MemoCandidate extends Method {
|
||||
MemoCandidate() { this = any(MemoStmt m).getEnclosingMethod() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if parameter `p` of `m` is read in the right hand side of `assign`.
|
||||
*/
|
||||
private predicate parameterUsedInMemoValue(Method m, Parameter p, MemoStmt a) {
|
||||
p = m.getAParameter() and
|
||||
a.getEnclosingMethod() = m and
|
||||
p.getAVariable().getAnAccess().getParent*() = a.getRightOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if parameter `p` of `m` is read in the left hand side of `assign`.
|
||||
*/
|
||||
private predicate parameterUsedInMemoKey(Method m, Parameter p, HashMemoStmt a) {
|
||||
p = m.getAParameter() and
|
||||
a.getEnclosingMethod() = m and
|
||||
p.getAVariable().getAnAccess().getParent+() = a.getLeftOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the assignment `s` is returned from its parent method `m`.
|
||||
*/
|
||||
private predicate memoReturnedFromMethod(Method m, MemoStmt s) {
|
||||
exists(DataFlow::Node n | n.asExpr().getExpr() = s and exprNodeReturnedFrom(n, m))
|
||||
or
|
||||
// If we don't have flow (e.g. due to the dataflow library not supporting instance variable flow yet),
|
||||
// fall back to a syntactic heuristic: does the last statement in the method mention the memoization variable?
|
||||
m.getLastStmt().getAChild*().(InstanceVariableReadAccess).getVariable() =
|
||||
s.getVariableAccess().getVariable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `m` is a memoization method with a parameter `p` which is not used in the memoization key.
|
||||
* This can cause stale or incorrect values to be returned when the method is called with different arguments.
|
||||
*/
|
||||
predicate isImproperMemoizationMethod(Method m, Parameter p, AssignLogicalOrExpr s) {
|
||||
m instanceof MemoCandidate and
|
||||
m.getName() != "initialize" and
|
||||
parameterUsedInMemoValue(m, p, s) and
|
||||
not parameterUsedInMemoKey(m, p, s) and
|
||||
memoReturnedFromMethod(m, s)
|
||||
}
|
||||
@@ -29,7 +29,9 @@ private string rankedInsecureAlgorithm(int i) {
|
||||
// weak hash algorithms and block modes as well.
|
||||
result =
|
||||
rank[i](string s |
|
||||
isWeakEncryptionAlgorithm(s) or isWeakHashingAlgorithm(s) or isWeakBlockMode(s)
|
||||
isWeakEncryptionAlgorithm(s) or
|
||||
isWeakHashingAlgorithm(s) or
|
||||
s.(Cryptography::BlockMode).isWeak()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -329,13 +331,9 @@ private API::Node cipherApi() {
|
||||
result = API::getTopLevelMember("OpenSSL").getMember("Cipher").getMember("Cipher")
|
||||
}
|
||||
|
||||
private class BlockMode extends string {
|
||||
BlockMode() { this = ["ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR"] }
|
||||
}
|
||||
|
||||
private newtype TCipherMode =
|
||||
TStreamCipher() or
|
||||
TBlockMode(BlockMode blockMode)
|
||||
TBlockMode(Cryptography::BlockMode blockMode)
|
||||
|
||||
/**
|
||||
* Represents the mode used by this stream cipher.
|
||||
@@ -343,7 +341,8 @@ private newtype TCipherMode =
|
||||
* block mode.
|
||||
*/
|
||||
private class CipherMode extends TCipherMode {
|
||||
private BlockMode getBlockMode() { this = TBlockMode(result) }
|
||||
/** Gets the underlying block mode, if any. */
|
||||
Cryptography::BlockMode getBlockMode() { this = TBlockMode(result) }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
string toString() {
|
||||
@@ -360,7 +359,7 @@ private class CipherMode extends TCipherMode {
|
||||
predicate isBlockMode(string s) { this.getBlockMode() = s.toUpperCase() }
|
||||
|
||||
/** Holds if this cipher mode is a weak block mode. */
|
||||
predicate isWeak() { isWeakBlockMode(this.getBlockMode()) }
|
||||
predicate isWeak() { this.getBlockMode().isWeak() }
|
||||
}
|
||||
|
||||
private string getStringArgument(DataFlow::CallNode call, int i) {
|
||||
@@ -371,6 +370,25 @@ private int getIntArgument(DataFlow::CallNode call, int i) {
|
||||
result = call.getArgument(i).asExpr().getConstantValue().getInt()
|
||||
}
|
||||
|
||||
bindingset[blockCipherName]
|
||||
private Cryptography::BlockMode getCandidateBlockModeFromCipherName(string blockCipherName) {
|
||||
result = blockCipherName.splitAt("-", [1, 2]).toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block mode specified as part of a block cipher name used to
|
||||
* instantiate an `OpenSSL::Cipher` instance. If the block mode is not
|
||||
* explicitly specified, this defaults to "CBC".
|
||||
*/
|
||||
bindingset[blockCipherName]
|
||||
private Cryptography::BlockMode getBlockModeFromCipherName(string blockCipherName) {
|
||||
// Extract the block mode from the cipher name
|
||||
result = getCandidateBlockModeFromCipherName(blockCipherName)
|
||||
or
|
||||
// Fall back on the OpenSSL default of CBC if the block mode is unspecified
|
||||
not exists(getCandidateBlockModeFromCipherName(blockCipherName)) and result = "CBC"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is a call to `OpenSSL::Cipher.new` that instantiates a
|
||||
* `cipher` instance with mode `cipherMode`.
|
||||
@@ -382,8 +400,9 @@ private predicate cipherInstantiationGeneric(
|
||||
// `OpenSSL::Cipher.new('<cipherName>')`
|
||||
call = cipherApi().getAnInstantiation() and
|
||||
cipherName = getStringArgument(call, 0) and
|
||||
// CBC is used by default
|
||||
cipherMode.isBlockMode("CBC")
|
||||
if cipher.getAlgorithm().isStreamCipher()
|
||||
then cipherMode = TStreamCipher()
|
||||
else cipherMode.isBlockMode(getBlockModeFromCipherName(cipherName))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -398,12 +417,12 @@ private predicate cipherInstantiationAES(
|
||||
exists(string cipherName | cipher.matchesName(cipherName) |
|
||||
// `OpenSSL::Cipher::AES` instantiations
|
||||
call = cipherApi().getMember("AES").getAnInstantiation() and
|
||||
exists(string keyLength, string blockMode |
|
||||
exists(string keyLength, Cryptography::BlockMode blockMode |
|
||||
// `OpenSSL::Cipher::AES.new('<keyLength-blockMode>')
|
||||
exists(string arg0 |
|
||||
arg0 = getStringArgument(call, 0) and
|
||||
keyLength = arg0.splitAt("-", 0) and
|
||||
blockMode = arg0.splitAt("-", 1).toUpperCase()
|
||||
blockMode = getBlockModeFromCipherName(arg0)
|
||||
)
|
||||
or
|
||||
// `OpenSSL::Cipher::AES.new(<keyLength>, '<blockMode>')`
|
||||
@@ -419,7 +438,7 @@ private predicate cipherInstantiationAES(
|
||||
call = cipherApi().getMember(mod).getAnInstantiation() and
|
||||
// Canonical representation is `AES-<keyLength>`
|
||||
blockAlgo = "AES-" + mod.suffix(3) and
|
||||
exists(string blockMode |
|
||||
exists(Cryptography::BlockMode blockMode |
|
||||
if exists(getStringArgument(call, 0))
|
||||
then
|
||||
// `OpenSSL::Cipher::<blockAlgo>.new('<blockMode>')`
|
||||
@@ -446,7 +465,7 @@ private predicate cipherInstantiationSpecific(
|
||||
// Block ciphers with dedicated modules
|
||||
exists(string blockAlgo | blockAlgo = ["BF", "CAST5", "DES", "IDEA", "RC2"] |
|
||||
call = cipherApi().getMember(blockAlgo).getAnInstantiation() and
|
||||
exists(string blockMode |
|
||||
exists(Cryptography::BlockMode blockMode |
|
||||
if exists(getStringArgument(call, 0))
|
||||
then
|
||||
// `OpenSSL::Cipher::<blockAlgo>.new('<blockMode>')`
|
||||
@@ -528,25 +547,25 @@ private class CipherNode extends DataFlow::Node {
|
||||
private class CipherOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallNode {
|
||||
private CipherNode cipherNode;
|
||||
private DataFlow::Node input;
|
||||
|
||||
CipherOperation() {
|
||||
// cipher instantiation is counted as a cipher operation with no input
|
||||
cipherNode = this and cipherNode instanceof CipherInstantiation
|
||||
or
|
||||
this.getReceiver() = cipherNode and
|
||||
this.getMethodName() = "update" and
|
||||
input = this.getArgument(0)
|
||||
this.getMethodName() = "update"
|
||||
}
|
||||
|
||||
override Cryptography::EncryptionAlgorithm getAlgorithm() {
|
||||
result = cipherNode.getCipher().getAlgorithm()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
override DataFlow::Node getAnInput() {
|
||||
this.getMethodName() = "update" and
|
||||
result = this.getArgument(0)
|
||||
}
|
||||
|
||||
override predicate isWeak() {
|
||||
cipherNode.getCipher().isWeak() or
|
||||
cipherNode.getCipherMode().isWeak()
|
||||
override Cryptography::BlockMode getBlockMode() {
|
||||
result = cipherNode.getCipherMode().getBlockMode()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,16 @@ module PathInjection {
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for path injection vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for path injection vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -43,12 +50,12 @@ module PathInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* An inclusion check against an array of constant strings, considered as a
|
||||
* sanitizer-guard.
|
||||
*/
|
||||
class StringConstArrayInclusionCallAsSanitizerGuard extends SanitizerGuard,
|
||||
StringConstArrayInclusionCall { }
|
||||
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier { }
|
||||
}
|
||||
|
||||
@@ -23,9 +23,11 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof PathInjection::Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Path::PathSanitization }
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof Path::PathSanitization or node instanceof PathInjection::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof PathInjection::SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ module ReflectedXss {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ module ServerSideRequestForgery {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "URL redirection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for server side request forgery. */
|
||||
class RemoteFlowSourceAsSource extends Source {
|
||||
|
||||
@@ -22,11 +22,13 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof Sanitizer or
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard or
|
||||
guard instanceof StringConstCompare or
|
||||
guard instanceof StringConstArrayInclusionCall
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ module StoredXss {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ module UrlRedirect {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "URL redirection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* Additional taint steps for "URL redirection" vulnerabilities.
|
||||
@@ -77,7 +79,7 @@ module UrlRedirect {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* Some methods will propagate taint to their return values.
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,11 @@ private module Shared {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "server-side cross-site scripting" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
private class ErbOutputMethodCallArgumentNode extends DataFlow::Node {
|
||||
private MethodCall call;
|
||||
@@ -93,13 +95,13 @@ private module Shared {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* An inclusion check against an array of constant strings, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstArrayInclusionCallAsSanitizerGuard extends SanitizerGuard,
|
||||
StringConstArrayInclusionCall { }
|
||||
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier { }
|
||||
|
||||
/**
|
||||
* A `VariableWriteAccessCfgNode` that is not succeeded (locally) by another
|
||||
@@ -274,8 +276,12 @@ module ReflectedXss {
|
||||
/** A sanitizer for stored XSS vulnerabilities. */
|
||||
class Sanitizer = Shared::Sanitizer;
|
||||
|
||||
/** A sanitizer guard for stored XSS vulnerabilities. */
|
||||
class SanitizerGuard = Shared::SanitizerGuard;
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for stored XSS vulnerabilities.
|
||||
*/
|
||||
deprecated class SanitizerGuard = Shared::SanitizerGuard;
|
||||
|
||||
/**
|
||||
* An additional step that is preserves dataflow in the context of reflected XSS.
|
||||
@@ -329,8 +335,12 @@ module StoredXss {
|
||||
/** A sanitizer for stored XSS vulnerabilities. */
|
||||
class Sanitizer = Shared::Sanitizer;
|
||||
|
||||
/** A sanitizer guard for stored XSS vulnerabilities. */
|
||||
class SanitizerGuard = Shared::SanitizerGuard;
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for stored XSS vulnerabilities.
|
||||
*/
|
||||
deprecated class SanitizerGuard = Shared::SanitizerGuard;
|
||||
|
||||
/**
|
||||
* An additional step that preserves dataflow in the context of stored XSS.
|
||||
|
||||
@@ -36,7 +36,7 @@ module CleartextSources {
|
||||
* sensitive data with a call to `sub`.
|
||||
*/
|
||||
private predicate effectiveSubRegExp(CfgNodes::ExprNodes::RegExpLiteralCfgNode re) {
|
||||
re.getConstantValue().getStringlikeValue().matches([".*", ".+"])
|
||||
re.getConstantValue().getStringlikeValue() = [".*", ".+"]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ module CleartextSources {
|
||||
* sensitive data with a call to `gsub`.
|
||||
*/
|
||||
private predicate effectiveGsubRegExp(CfgNodes::ExprNodes::RegExpLiteralCfgNode re) {
|
||||
re.getConstantValue().getStringlikeValue().matches(".")
|
||||
re.getConstantValue().getStringlikeValue() = "."
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,6 @@ predicate isStrongPasswordHashingAlgorithm(string name) {
|
||||
predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
|
||||
|
||||
/**
|
||||
* Holds if `name` corresponds to a weak block cipher mode of operation.
|
||||
* Holds if `name` corresponds to a stream cipher.
|
||||
*/
|
||||
predicate isWeakBlockMode(string name) { name = "ECB" }
|
||||
predicate isStreamCipher(string name) { name = ["CHACHA", "RC4", "ARC4", "ARCFOUR", "RABBIT"] }
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
* either a single character, a set of characters represented by a
|
||||
* character class, or the set of all characters.
|
||||
* * The product automaton is constructed lazily, starting with pair states
|
||||
* `(q, q)` where `q` is a fork, and proceding along an over-approximate
|
||||
* `(q, q)` where `q` is a fork, and proceeding along an over-approximate
|
||||
* step relation.
|
||||
* * The over-approximate step relation allows transitions along pairs of
|
||||
* abstract input symbols where the symbols have overlap in the characters they accept.
|
||||
@@ -141,26 +141,28 @@ private class StatePair extends TStatePair {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds for all constructed state pairs.
|
||||
* Holds for `(fork, fork)` state pairs when `isFork(fork, _, _, _, _)` holds.
|
||||
*
|
||||
* Used in `statePairDist`
|
||||
* Used in `statePairDistToFork`
|
||||
*/
|
||||
private predicate isStatePair(StatePair p) { any() }
|
||||
private predicate isStatePairFork(StatePair p) {
|
||||
exists(State fork | p = MkStatePair(fork, fork) and isFork(fork, _, _, _, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r`.
|
||||
*
|
||||
* Used in `statePairDist`
|
||||
* Used in `statePairDistToFork`
|
||||
*/
|
||||
private predicate delta2(StatePair q, StatePair r) { step(q, _, _, r) }
|
||||
private predicate reverseStep(StatePair r, StatePair q) { step(q, _, _, r) }
|
||||
|
||||
/**
|
||||
* Gets the minimum length of a path from `q` to `r` in the
|
||||
* product automaton.
|
||||
*/
|
||||
private int statePairDist(StatePair q, StatePair r) =
|
||||
shortestDistances(isStatePair/1, delta2/2)(q, r, result)
|
||||
private int statePairDistToFork(StatePair q, StatePair r) =
|
||||
shortestDistances(isStatePairFork/1, reverseStep/2)(r, q, result)
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from `q` to `r1` and from `q` to `r2`
|
||||
@@ -255,14 +257,7 @@ private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, St
|
||||
|
||||
private newtype TTrace =
|
||||
Nil() or
|
||||
Step(InputSymbol s1, InputSymbol s2, TTrace t) {
|
||||
exists(StatePair p |
|
||||
isReachableFromFork(_, p, t, _) and
|
||||
step(p, s1, s2, _)
|
||||
)
|
||||
or
|
||||
t = Nil() and isFork(_, s1, s2, _, _)
|
||||
}
|
||||
Step(InputSymbol s1, InputSymbol s2, TTrace t) { isReachableFromFork(_, _, s1, s2, t, _) }
|
||||
|
||||
/**
|
||||
* A list of pairs of input symbols that describe a path in the product automaton
|
||||
@@ -284,20 +279,28 @@ private class Trace extends TTrace {
|
||||
* a path from `r` back to `(fork, fork)` with `rem` steps.
|
||||
*/
|
||||
private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) {
|
||||
exists(InputSymbol s1, InputSymbol s2, Trace v |
|
||||
isReachableFromFork(fork, r, s1, s2, v, rem) and
|
||||
w = Step(s1, s2, v)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isReachableFromFork(
|
||||
State fork, StatePair r, InputSymbol s1, InputSymbol s2, Trace v, int rem
|
||||
) {
|
||||
// base case
|
||||
exists(InputSymbol s1, InputSymbol s2, State q1, State q2 |
|
||||
exists(State q1, State q2 |
|
||||
isFork(fork, s1, s2, q1, q2) and
|
||||
r = MkStatePair(q1, q2) and
|
||||
w = Step(s1, s2, Nil()) and
|
||||
rem = statePairDist(r, MkStatePair(fork, fork))
|
||||
v = Nil() and
|
||||
rem = statePairDistToFork(r, MkStatePair(fork, fork))
|
||||
)
|
||||
or
|
||||
// recursive case
|
||||
exists(StatePair p, Trace v, InputSymbol s1, InputSymbol s2 |
|
||||
exists(StatePair p |
|
||||
isReachableFromFork(fork, p, v, rem + 1) and
|
||||
step(p, s1, s2, r) and
|
||||
w = Step(s1, s2, v) and
|
||||
rem >= statePairDist(r, MkStatePair(fork, fork))
|
||||
rem = statePairDistToFork(r, MkStatePair(fork, fork))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,12 @@ module PolynomialReDoS {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for polynomial regular expression denial of service
|
||||
* vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -108,23 +110,21 @@ module PolynomialReDoS {
|
||||
override DataFlow::Node getHighlight() { result = matchNode }
|
||||
}
|
||||
|
||||
private predicate lengthGuard(CfgNodes::ExprCfgNode g, CfgNode node, boolean branch) {
|
||||
exists(DataFlow::Node input, DataFlow::CallNode length, DataFlow::ExprNode operand |
|
||||
length.asExpr().getExpr().(AST::MethodCall).getMethodName() = "length" and
|
||||
length.getReceiver() = input and
|
||||
length.flowsTo(operand) and
|
||||
operand.getExprNode() = g.(CfgNodes::ExprNodes::RelationalOperationCfgNode).getAnOperand() and
|
||||
node = input.asExpr() and
|
||||
branch = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A check on the length of a string, seen as a sanitizer guard.
|
||||
*/
|
||||
class LengthGuard extends SanitizerGuard, CfgNodes::ExprNodes::RelationalOperationCfgNode {
|
||||
private DataFlow::Node input;
|
||||
|
||||
LengthGuard() {
|
||||
exists(DataFlow::CallNode length, DataFlow::ExprNode operand |
|
||||
length.asExpr().getExpr().(AST::MethodCall).getMethodName() = "length" and
|
||||
length.getReceiver() = input and
|
||||
length.flowsTo(operand) and
|
||||
operand.getExprNode() = this.getAnOperand()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate checks(CfgNode node, boolean branch) {
|
||||
node = input.asExpr() and branch = true
|
||||
}
|
||||
class LengthGuard extends Sanitizer {
|
||||
LengthGuard() { this = DataFlow::BarrierGuard<lengthGuard/3>::getABarrierNode() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ module PolynomialReDoS {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard node) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard node) {
|
||||
node instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ module RegExpInjection {
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for regexp injection vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A data flow sanitized for regexp injection vulnerabilities.
|
||||
@@ -64,14 +66,14 @@ module RegExpInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* An inclusion check against an array of constant strings, considered as a
|
||||
* sanitizer-guard.
|
||||
*/
|
||||
class StringConstArrayInclusionCallAsSanitizerGuard extends SanitizerGuard,
|
||||
StringConstArrayInclusionCall { }
|
||||
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier { }
|
||||
|
||||
/**
|
||||
* A call to `Regexp.escape` (or its alias, `Regexp.quote`), considered as a
|
||||
|
||||
@@ -20,7 +20,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof RegExpInjection::Sink }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof RegExpInjection::SanitizerGuard
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.2.1-dev
|
||||
version: 0.3.0-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
|
||||
@@ -1356,24 +1356,40 @@ ruby_ast_node_info(
|
||||
int loc: @location ref
|
||||
);
|
||||
|
||||
erb_comment_directive_child(
|
||||
unique int erb_comment_directive: @erb_comment_directive ref,
|
||||
unique int child: @erb_token_comment ref
|
||||
);
|
||||
|
||||
erb_comment_directive_def(
|
||||
unique int id: @erb_comment_directive,
|
||||
int child: @erb_token_comment ref
|
||||
unique int id: @erb_comment_directive
|
||||
);
|
||||
|
||||
erb_directive_child(
|
||||
unique int erb_directive: @erb_directive ref,
|
||||
unique int child: @erb_token_code ref
|
||||
);
|
||||
|
||||
erb_directive_def(
|
||||
unique int id: @erb_directive,
|
||||
int child: @erb_token_code ref
|
||||
unique int id: @erb_directive
|
||||
);
|
||||
|
||||
erb_graphql_directive_child(
|
||||
unique int erb_graphql_directive: @erb_graphql_directive ref,
|
||||
unique int child: @erb_token_code ref
|
||||
);
|
||||
|
||||
erb_graphql_directive_def(
|
||||
unique int id: @erb_graphql_directive,
|
||||
int child: @erb_token_code ref
|
||||
unique int id: @erb_graphql_directive
|
||||
);
|
||||
|
||||
erb_output_directive_child(
|
||||
unique int erb_output_directive: @erb_output_directive ref,
|
||||
unique int child: @erb_token_code ref
|
||||
);
|
||||
|
||||
erb_output_directive_def(
|
||||
unique int id: @erb_output_directive,
|
||||
int child: @erb_token_code ref
|
||||
unique int id: @erb_output_directive
|
||||
);
|
||||
|
||||
@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
description: Update ERB parser
|
||||
compatibility: full
|
||||
erb_comment_directive_def.rel: reorder erb_comment_directive_def.rel (int id, int child) id
|
||||
erb_comment_directive_child.rel: reorder erb_comment_directive_def.rel (int id, int child) id child
|
||||
|
||||
erb_directive_def.rel: reorder erb_directive_def.rel (int id, int child) id
|
||||
erb_directive_child.rel: reorder erb_directive_def.rel (int id, int child) id child
|
||||
|
||||
erb_graphql_directive_def.rel: reorder erb_graphql_directive_def.rel (int id, int child) id
|
||||
erb_graphql_directive_child.rel: reorder erb_graphql_directive_def.rel (int id, int child) id child
|
||||
|
||||
erb_output_directive_def.rel: reorder erb_output_directive_def.rel (int id, int child) id
|
||||
erb_output_directive_child.rel: reorder erb_output_directive_def.rel (int id, int child) id child
|
||||
@@ -1,3 +1,9 @@
|
||||
## 0.1.4
|
||||
|
||||
## 0.1.3
|
||||
|
||||
## 0.1.2
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### New Queries
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now report if a cryptographic operation is potentially insecure due to use of a weak block mode.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.
|
||||
1
ruby/ql/src/change-notes/released/0.1.2.md
Normal file
1
ruby/ql/src/change-notes/released/0.1.2.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.2
|
||||
1
ruby/ql/src/change-notes/released/0.1.3.md
Normal file
1
ruby/ql/src/change-notes/released/0.1.3.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.3
|
||||
1
ruby/ql/src/change-notes/released/0.1.4.md
Normal file
1
ruby/ql/src/change-notes/released/0.1.4.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.4
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.1
|
||||
lastReleaseVersion: 0.1.4
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Decompression of user-controlled data without taking proper precaution can
|
||||
result in uncontrolled and massive decompression on the server, resulting
|
||||
in a denial of service.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
When decompressing files supplied by the user, make sure that you're checking
|
||||
the size of the incoming data chunks before writing to an output.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In this example, the size of the input buffer chunks and total size are checked before each chunk is written to the output.
|
||||
</p>
|
||||
<sample src="examples/decompress.rb" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @name User-controlled file decompression
|
||||
* @description User-controlled data that flows into decompression library APIs could be dangerous
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.8
|
||||
* @precision medium
|
||||
* @id rb/user-controlled-file-decompression
|
||||
* @tags security external/cwe/cwe-409
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class DecompressionApiUse extends DataFlow::Node {
|
||||
private DataFlow::CallNode call;
|
||||
|
||||
// this should find the first argument in calls to Zlib::Inflate.inflate or Zip::File.open_buffer
|
||||
DecompressionApiUse() {
|
||||
this = call.getArgument(0) and
|
||||
(
|
||||
call = API::getTopLevelMember("Zlib").getMember("Inflate").getAMethodCall("inflate") or
|
||||
call = API::getTopLevelMember("Zip").getMember("File").getAMethodCall("open_buffer")
|
||||
)
|
||||
}
|
||||
|
||||
// returns calls to Zlib::Inflate.inflate or Zip::File.open_buffer
|
||||
DataFlow::CallNode getCall() { result = call }
|
||||
}
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "DecompressionApiUse" }
|
||||
|
||||
// this predicate will be used to constrain our query to find instances where only remote user-controlled data flows to the sink
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
// our Decompression APIs defined above will be the sinks we use for this query
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionApiUse }
|
||||
}
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode().(DecompressionApiUse), source, sink,
|
||||
"This call to $@ is unsafe because user-controlled data is used to set the object being decompressed, which could lead to a denial of service attack or malicious code extracted from an unknown source.",
|
||||
sink.getNode().(DecompressionApiUse).getCall(),
|
||||
sink.getNode().(DecompressionApiUse).getCall().getMethodName()
|
||||
@@ -0,0 +1,17 @@
|
||||
class UsersController < ActionController::Base
|
||||
def example_zlib_inflate
|
||||
MAX_ALLOWED_CHUNK_SIZE = 256
|
||||
MAX_ALLOWED_TOTAL_SIZE = 1024
|
||||
|
||||
user_data = params[:data]
|
||||
output = []
|
||||
outsize = 0
|
||||
|
||||
Zlib::Inflate.inflate(user_data) { |chunk|
|
||||
outsize += chunk.size
|
||||
if chunk.size < MAX_ALLOWED_CHUNK_SIZE && outsize < MAX_ALLOWED_TOTAL_SIZE
|
||||
output << chunk
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user