merge in main

This commit is contained in:
yoff
2022-06-23 09:05:32 +00:00
committed by GitHub
parent 8bf60301da
commit 140dc1a61e
4448 changed files with 340100 additions and 31408 deletions

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

View File

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

View File

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

View File

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

View File

@@ -9,5 +9,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
n instanceof BlockArgumentNode
or
n instanceof SummaryNode
or
n instanceof HashSplatArgumentsNode
}
}

View File

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

View File

@@ -1,4 +0,0 @@
---
category: fix
---
The Tree-sitter Ruby grammar has been updated; this fixes several issues where Ruby code was parsed incorrectly.

View File

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

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

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

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.0
lastReleaseVersion: 0.2.3

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,3 +4,4 @@
*/
import codeql.ruby.DataFlow
import codeql.ruby.security.CryptoAlgorithms as CryptoAlgorithms

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,9 @@
## 0.1.4
## 0.1.3
## 0.1.2
## 0.1.1
### New Queries

View File

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

View File

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

View File

@@ -0,0 +1 @@
## 0.1.2

View File

@@ -0,0 +1 @@
## 0.1.3

View File

@@ -0,0 +1 @@
## 0.1.4

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.1.1
lastReleaseVersion: 0.1.4

View File

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

View File

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

View File

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