Merge remote-tracking branch 'origin/main' into nickrolfe/regex_injection

This commit is contained in:
Nick Rolfe
2021-11-22 17:05:27 +00:00
649 changed files with 26534 additions and 6327 deletions

View File

@@ -252,8 +252,8 @@ module API {
* Holds if `ref` is a use of a node that should have an incoming edge from the root
* node labeled `lbl` in the API graph.
*/
cached
predicate useRoot(string lbl, DataFlow::Node ref) {
pragma[nomagic]
private predicate useRoot(string lbl, DataFlow::Node ref) {
exists(string name, ExprNodes::ConstantAccessCfgNode access, ConstantReadAccess read |
access = ref.asExpr() and
lbl = Label::member(read.getName()) and
@@ -268,49 +268,37 @@ module API {
}
/**
* Holds if `ref` is a use of a node that should have an incoming edge from use node
* `base` labeled `lbl` in the API graph.
* Holds if `ref` is a use of a node that should have an incoming edge labeled `lbl`,
* from a use node that flows to `node`.
*/
cached
predicate useUse(DataFlow::LocalSourceNode base, string lbl, DataFlow::Node ref) {
exists(ExprCfgNode node |
// First, we find a predecessor of the node `ref` that we want to determine. The predecessor
// is any node that is a type-tracked use of a data flow node (`src`), which is itself a
// reference to the API node `base`. Thus, `pred` and `src` both represent uses of `base`.
//
// Once we have identified the predecessor, we define its relation to the successor `ref` as
// well as the label on the edge from `pred` to `ref`. This label describes the nature of
// the relationship between `pred` and `ref`.
useExpr(node, base)
|
// // Referring to an attribute on a node that is a use of `base`:
// pred = `Rails` part of `Rails::Whatever`
// lbl = `Whatever`
// ref = `Rails::Whatever`
exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
not exists(resolveTopLevel(read)) and
node = c.getScopeExpr() and
lbl = Label::member(read.getName()) and
ref.asExpr() = c and
read = c.getExpr()
)
or
// Calling a method on a node that is a use of `base`
exists(ExprNodes::MethodCallCfgNode call, string name |
node = call.getReceiver() and
name = call.getExpr().getMethodName() and
lbl = Label::return(name) and
name != "new" and
ref.asExpr() = call
)
or
// Calling the `new` method on a node that is a use of `base`, which creates a new instance
exists(ExprNodes::MethodCallCfgNode call |
node = call.getReceiver() and
lbl = Label::instance() and
call.getExpr().getMethodName() = "new" and
ref.asExpr() = call
)
private predicate useStep(string lbl, ExprCfgNode node, DataFlow::Node ref) {
// // Referring to an attribute on a node that is a use of `base`:
// pred = `Rails` part of `Rails::Whatever`
// lbl = `Whatever`
// ref = `Rails::Whatever`
exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
not exists(resolveTopLevel(read)) and
node = c.getScopeExpr() and
lbl = Label::member(read.getName()) and
ref.asExpr() = c and
read = c.getExpr()
)
or
// Calling a method on a node that is a use of `base`
exists(ExprNodes::MethodCallCfgNode call, string name |
node = call.getReceiver() and
name = call.getExpr().getMethodName() and
lbl = Label::return(name) and
name != "new" and
ref.asExpr() = call
)
or
// Calling the `new` method on a node that is a use of `base`, which creates a new instance
exists(ExprNodes::MethodCallCfgNode call |
node = call.getReceiver() and
lbl = Label::instance() and
call.getExpr().getMethodName() = "new" and
ref.asExpr() = call
)
}
@@ -318,14 +306,10 @@ module API {
private predicate isUse(DataFlow::Node nd) {
useRoot(_, nd)
or
useUse(_, _, nd)
}
pragma[nomagic]
private predicate useExpr(ExprCfgNode node, DataFlow::LocalSourceNode src) {
exists(DataFlow::LocalSourceNode pred |
pred = trackUseNode(src) and
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node))
exists(ExprCfgNode node, DataFlow::LocalSourceNode pred |
pred = useCandFwd() and
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
useStep(_, node, nd)
)
}
@@ -335,26 +319,54 @@ module API {
cached
predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
/**
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
*
* The flow from `src` to that node may be inter-procedural.
*/
private DataFlow::LocalSourceNode trackUseNode(DataFlow::Node src, TypeTracker t) {
// Declaring `src` to be a `LocalSourceNode` currently causes a redundant check in the
// recursive case, so instead we check it explicitly here.
src instanceof DataFlow::LocalSourceNode and
private DataFlow::LocalSourceNode useCandFwd(TypeTracker t) {
t.start() and
isUse(src) and
result = src
isUse(result)
or
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
exists(TypeTracker t2 | result = useCandFwd(t2).track(t2, t))
}
private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) }
private DataFlow::Node useCandRev(TypeBackTracker tb) {
result = useCandFwd() and
tb.start()
or
exists(TypeBackTracker tb2, DataFlow::LocalSourceNode mid, TypeTracker t |
mid = useCandRev(tb2) and
result = mid.backtrack(tb2, tb) and
pragma[only_bind_out](result) = useCandFwd(t) and
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
)
}
private DataFlow::LocalSourceNode useCandRev() {
result = useCandRev(TypeBackTracker::end()) and
isUse(result)
}
/**
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
*
* The flow from `src` to that node may be inter-procedural.
* The flow from `src` to the returned node may be inter-procedural.
*/
private DataFlow::Node trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
result = src and
result = useCandRev() and
t.start()
or
exists(TypeTracker t2, DataFlow::LocalSourceNode mid, TypeBackTracker tb |
mid = trackUseNode(src, t2) and
result = mid.track(t2, t) and
pragma[only_bind_out](result) = useCandRev(tb) and
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
)
}
/**
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
*
* The flow from `src` to the returned node may be inter-procedural.
*/
cached
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
@@ -371,9 +383,10 @@ module API {
pred = MkRoot() and
useRoot(lbl, ref)
or
exists(DataFlow::Node nd |
pred = MkUse(nd) and
useUse(nd, lbl, ref)
exists(ExprCfgNode node, DataFlow::Node src |
pred = MkUse(src) and
trackUseNode(src).flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
useStep(lbl, node, ref)
)
)
}

View File

@@ -417,6 +417,12 @@ module HTTP {
/** Gets a node which returns the body of the response */
DataFlow::Node getResponseBody() { result = super.getResponseBody() }
/**
* Gets a node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
DataFlow::Node getURL() { result = super.getURL() }
/** Gets a string that identifies the framework used for this request. */
string getFramework() { result = super.getFramework() }
@@ -442,6 +448,12 @@ module HTTP {
/** Gets a node which returns the body of the response */
abstract DataFlow::Node getResponseBody();
/**
* Gets a node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
abstract DataFlow::Node getURL();
/** Gets a string that identifies the framework used for this request. */
abstract string getFramework();

View File

@@ -1,5 +1,5 @@
private import codeql.ruby.AST
private import codeql.ruby.regexp.RegExpTreeView as RETV
private import codeql.ruby.security.performance.RegExpTreeView as RETV
private import internal.AST
private import internal.Scope
private import internal.TreeSitter

View File

@@ -167,7 +167,7 @@ private module Cached {
TLTExpr(Ruby::Binary g) { g instanceof @ruby_binary_langle } or
TLambda(Ruby::Lambda g) or
TLeftAssignmentList(Ruby::LeftAssignmentList g) or
TLocalVariableAccessReal(Ruby::Identifier g, AST::LocalVariable v) {
TLocalVariableAccessReal(Ruby::Identifier g, TLocalVariableReal v) {
LocalVariableAccess::range(g, v)
} or
TLocalVariableAccessSynth(AST::AstNode parent, int i, AST::LocalVariable v) {
@@ -284,13 +284,55 @@ private module Cached {
TWhileModifierExpr(Ruby::WhileModifier g) or
TYieldCall(Ruby::Yield g)
class TAstNodeReal =
TAddExprReal or TAliasStmt or TArgumentList or TAssignAddExpr or TAssignBitwiseAndExpr or
TAssignBitwiseOrExpr or TAssignBitwiseXorExpr or TAssignDivExpr or TAssignExponentExpr or
TAssignExprReal or TAssignLShiftExpr or TAssignLogicalAndExpr or TAssignLogicalOrExpr or
TAssignModuloExpr or TAssignMulExpr or TAssignRShiftExpr or TAssignSubExpr or
TBareStringLiteral or TBareSymbolLiteral or TBeginBlock or TBeginExpr or
TBitwiseAndExprReal or TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or
TBlockParameter or TBraceBlock or TBreakStmt or TCaseEqExpr or TCaseExpr or
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
TDivExprReal or TDo or TDoBlock or TElementReference or TElse or TElsif or TEmptyStmt or
TEndBlock or TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFloatLiteral or
TForExpr or TForIn or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashSplatExpr or
THashSplatParameter or THereDoc or TIdentifierMethodCall or TIf or TIfModifierExpr or
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList 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
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 TScopeResolutionMethodCall or TSelfReal or
TSimpleParameter 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 TTuplePatternParameter or TUnaryMinusExpr or TUnaryPlusExpr or
TUndefStmt or TUnlessExpr or TUnlessModifierExpr or TUntilExpr or TUntilModifierExpr or
TWhenExpr or TWhileExpr or TWhileModifierExpr or TYieldCall;
class TAstNodeSynth =
TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
TBitwiseXorExprSynth 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 TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
/**
* Gets the underlying TreeSitter entity for a given AST node. This does not
* include synthesized AST nodes, because they are not the primary AST node
* for any given generated node.
*/
cached
Ruby::AstNode toGenerated(AST::AstNode n) {
Ruby::AstNode toGenerated(TAstNodeReal n) {
n = TAddExprReal(result) or
n = TAliasStmt(result) or
n = TArgumentList(result) or
@@ -495,7 +537,9 @@ private module Cached {
predicate synthChild(AST::AstNode parent, int i, AST::AstNode child) {
child = getSynthChild(parent, i)
or
any(Synthesis s).child(parent, i, RealChild(child))
any(Synthesis s).child(parent, i, RealChildRef(child))
or
any(Synthesis s).child(parent, i, SynthChildRef(child))
}
/**
@@ -527,7 +571,7 @@ private module Cached {
import Cached
TAstNode fromGenerated(Ruby::AstNode n) { n = toGenerated(result) }
TAstNodeReal fromGenerated(Ruby::AstNode n) { n = toGenerated(result) }
class TCall = TMethodCall or TYieldCall;

View File

@@ -106,6 +106,15 @@ private module Cached {
exists(string qname | qname = resolveConstant(r) and result = TResolved(qname))
}
pragma[nomagic]
private string constantDefinition1(ConstantReadAccess r) {
exists(ConstantWriteAccess w | result = constantDefinition0(w) |
r = w.getScopeExpr()
or
r = w.(ClassDeclaration).getSuperclassExpr()
)
}
/**
* Resolve constant access (class, module or otherwise) to a qualified module name.
* `resolveScopeExpr/1` picks the best (lowest priority number) result of
@@ -121,11 +130,7 @@ private module Cached {
isDefinedConstant(qn) and
qn = resolveScopeExpr(r, p) and
// prevent classes/modules that contain/extend themselves
not exists(ConstantWriteAccess w | qn = constantDefinition0(w) |
r = w.getScopeExpr()
or
r = w.(ClassDeclaration).getSuperclassExpr()
)
not qn = constantDefinition1(r)
|
qn order by p
)

View File

@@ -46,7 +46,25 @@ newtype SynthKind =
*/
newtype Child =
SynthChild(SynthKind k) or
RealChild(AstNode n)
RealChildRef(TAstNodeReal n) or
SynthChildRef(TAstNodeSynth n)
/**
* The purpose of this inlined predicate is to split up child references into
* those that are from real AST nodes (for which there will be no recursion
* through `RealChildRef`), and those that are synthesized recursively
* (for which there will be recursion through `SynthChildRef`).
*
* This performs much better than having a combined `ChildRef` that includes
* both real and synthesized AST nodes, since the recursion happening in
* `Synthesis::child/3` is non-linear.
*/
pragma[inline]
private Child childRef(TAstNode n) {
result = RealChildRef(n)
or
result = SynthChildRef(n)
}
private newtype TSynthesis = MkSynthesis()
@@ -125,7 +143,7 @@ private predicate assign(
child = SynthChild(LocalVariableAccessSynthKind(v))
or
i = 1 and
child = RealChild(value)
child = childRef(value)
)
}
@@ -218,10 +236,10 @@ private module SetterDesugar {
exists(AstNode call | call = TMethodCallSynth(seq, 0, _, _, _) |
parent = call and
i = 0 and
child = RealChild(sae.getReceiver())
child = childRef(sae.getReceiver())
or
parent = call and
child = RealChild(sae.getArgument(i - 1))
child = childRef(sae.getArgument(i - 1))
or
exists(int valueIndex | valueIndex = sae.getNumberOfArguments() + 1 |
parent = call and
@@ -234,7 +252,7 @@ private module SetterDesugar {
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sae, 0)))
or
i = 1 and
child = RealChild(sae.getRightOperand())
child = childRef(sae.getRightOperand())
)
)
)
@@ -357,7 +375,7 @@ private module AssignOperationDesugar {
exists(AstNode assign | assign = TAssignExprSynth(vao, -1) |
parent = assign and
i = 0 and
child = RealChild(vao.getLeftOperand())
child = childRef(vao.getLeftOperand())
or
parent = assign and
i = 1 and
@@ -369,7 +387,7 @@ private module AssignOperationDesugar {
child = SynthChild(vao.getVariableAccessKind())
or
i = 1 and
child = RealChild(vao.getRightOperand())
child = childRef(vao.getRightOperand())
)
)
)
@@ -477,7 +495,7 @@ private module AssignOperationDesugar {
or
parent = op and
i = 1 and
child = RealChild(sao.getRightOperand())
child = childRef(sao.getRightOperand())
)
)
or
@@ -648,7 +666,7 @@ private module CompoundAssignDesugar {
or
parent = TSplatExprSynth(assign, 1) and
i = 0 and
child = RealChild(tae.getRightOperand())
child = childRef(tae.getRightOperand())
)
or
exists(Pattern p, int j, int restIndex |
@@ -662,7 +680,7 @@ private module CompoundAssignDesugar {
exists(AstNode assign | assign = TAssignExprSynth(seq, j + 1) |
parent = assign and
i = 0 and
child = RealChild(p)
child = childRef(p)
or
parent = assign and
i = 1 and
@@ -767,7 +785,7 @@ private module ArrayLiteralDesugar {
child = SynthChild(ConstantReadAccessKind("::Array"))
or
parent = mc and
child = RealChild(al.getElement(i - 1))
child = childRef(al.getElement(i - 1))
)
)
}

View File

@@ -3,8 +3,7 @@
* Automatically generated from the tree-sitter grammar; do not edit
*/
private import codeql.files.FileSystem
private import codeql.Locations
import codeql.Locations as L
module Ruby {
/** The base class for all AST nodes */
@@ -13,7 +12,7 @@ module Ruby {
string toString() { result = this.getAPrimaryQlClass() }
/** Gets the location of this element. */
Location getLocation() { none() }
L::Location getLocation() { none() }
/** Gets the parent of this element. */
AstNode getParent() { ruby_ast_node_parent(this, result, _) }
@@ -37,7 +36,7 @@ module Ruby {
string getValue() { ruby_tokeninfo(this, _, result, _) }
/** Gets the location of this token. */
override Location getLocation() { ruby_tokeninfo(this, _, _, result) }
override L::Location getLocation() { ruby_tokeninfo(this, _, _, result) }
/** Gets a string representation of this element. */
override string toString() { result = this.getValue() }
@@ -70,7 +69,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Alias" }
/** Gets the location of this element. */
override Location getLocation() { ruby_alias_def(this, _, _, result) }
override L::Location getLocation() { ruby_alias_def(this, _, _, result) }
/** Gets the node corresponding to the field `alias`. */
UnderscoreMethodName getAlias() { ruby_alias_def(this, result, _, _) }
@@ -90,7 +89,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "ArgumentList" }
/** Gets the location of this element. */
override Location getLocation() { ruby_argument_list_def(this, result) }
override L::Location getLocation() { ruby_argument_list_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_argument_list_child(this, i, result) }
@@ -105,7 +104,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Array" }
/** Gets the location of this element. */
override Location getLocation() { ruby_array_def(this, result) }
override L::Location getLocation() { ruby_array_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_array_child(this, i, result) }
@@ -120,7 +119,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Assignment" }
/** Gets the location of this element. */
override Location getLocation() { ruby_assignment_def(this, _, _, result) }
override L::Location getLocation() { ruby_assignment_def(this, _, _, result) }
/** Gets the node corresponding to the field `left`. */
AstNode getLeft() { ruby_assignment_def(this, result, _, _) }
@@ -140,7 +139,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "BareString" }
/** Gets the location of this element. */
override Location getLocation() { ruby_bare_string_def(this, result) }
override L::Location getLocation() { ruby_bare_string_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_bare_string_child(this, i, result) }
@@ -155,7 +154,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "BareSymbol" }
/** Gets the location of this element. */
override Location getLocation() { ruby_bare_symbol_def(this, result) }
override L::Location getLocation() { ruby_bare_symbol_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_bare_symbol_child(this, i, result) }
@@ -170,7 +169,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Begin" }
/** Gets the location of this element. */
override Location getLocation() { ruby_begin_def(this, result) }
override L::Location getLocation() { ruby_begin_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_begin_child(this, i, result) }
@@ -185,7 +184,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "BeginBlock" }
/** Gets the location of this element. */
override Location getLocation() { ruby_begin_block_def(this, result) }
override L::Location getLocation() { ruby_begin_block_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_begin_block_child(this, i, result) }
@@ -200,7 +199,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Binary" }
/** Gets the location of this element. */
override Location getLocation() { ruby_binary_def(this, _, _, _, result) }
override L::Location getLocation() { ruby_binary_def(this, _, _, _, result) }
/** Gets the node corresponding to the field `left`. */
AstNode getLeft() { ruby_binary_def(this, result, _, _, _) }
@@ -275,7 +274,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Block" }
/** Gets the location of this element. */
override Location getLocation() { ruby_block_def(this, result) }
override L::Location getLocation() { ruby_block_def(this, result) }
/** Gets the node corresponding to the field `parameters`. */
BlockParameters getParameters() { ruby_block_parameters(this, result) }
@@ -295,7 +294,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "BlockArgument" }
/** Gets the location of this element. */
override Location getLocation() { ruby_block_argument_def(this, _, result) }
override L::Location getLocation() { ruby_block_argument_def(this, _, result) }
/** Gets the child of this node. */
UnderscoreArg getChild() { ruby_block_argument_def(this, result, _) }
@@ -310,7 +309,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "BlockParameter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_block_parameter_def(this, _, result) }
override L::Location getLocation() { ruby_block_parameter_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
Identifier getName() { ruby_block_parameter_def(this, result, _) }
@@ -325,7 +324,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "BlockParameters" }
/** Gets the location of this element. */
override Location getLocation() { ruby_block_parameters_def(this, result) }
override L::Location getLocation() { ruby_block_parameters_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_block_parameters_child(this, i, result) }
@@ -340,7 +339,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Break" }
/** Gets the location of this element. */
override Location getLocation() { ruby_break_def(this, result) }
override L::Location getLocation() { ruby_break_def(this, result) }
/** Gets the child of this node. */
ArgumentList getChild() { ruby_break_child(this, result) }
@@ -355,7 +354,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Call" }
/** Gets the location of this element. */
override Location getLocation() { ruby_call_def(this, _, result) }
override L::Location getLocation() { ruby_call_def(this, _, result) }
/** Gets the node corresponding to the field `arguments`. */
ArgumentList getArguments() { ruby_call_arguments(this, result) }
@@ -384,7 +383,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Case" }
/** Gets the location of this element. */
override Location getLocation() { ruby_case_def(this, result) }
override L::Location getLocation() { ruby_case_def(this, result) }
/** Gets the node corresponding to the field `value`. */
UnderscoreStatement getValue() { ruby_case_value(this, result) }
@@ -404,7 +403,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "ChainedString" }
/** Gets the location of this element. */
override Location getLocation() { ruby_chained_string_def(this, result) }
override L::Location getLocation() { ruby_chained_string_def(this, result) }
/** Gets the `i`th child of this node. */
String getChild(int i) { ruby_chained_string_child(this, i, result) }
@@ -425,7 +424,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Class" }
/** Gets the location of this element. */
override Location getLocation() { ruby_class_def(this, _, result) }
override L::Location getLocation() { ruby_class_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
AstNode getName() { ruby_class_def(this, result, _) }
@@ -468,7 +467,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Conditional" }
/** Gets the location of this element. */
override Location getLocation() { ruby_conditional_def(this, _, _, _, result) }
override L::Location getLocation() { ruby_conditional_def(this, _, _, _, result) }
/** Gets the node corresponding to the field `alternative`. */
UnderscoreArg getAlternative() { ruby_conditional_def(this, result, _, _, _) }
@@ -499,7 +498,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "DelimitedSymbol" }
/** Gets the location of this element. */
override Location getLocation() { ruby_delimited_symbol_def(this, result) }
override L::Location getLocation() { ruby_delimited_symbol_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_delimited_symbol_child(this, i, result) }
@@ -514,7 +513,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "DestructuredLeftAssignment" }
/** Gets the location of this element. */
override Location getLocation() { ruby_destructured_left_assignment_def(this, result) }
override L::Location getLocation() { ruby_destructured_left_assignment_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_destructured_left_assignment_child(this, i, result) }
@@ -529,7 +528,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "DestructuredParameter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_destructured_parameter_def(this, result) }
override L::Location getLocation() { ruby_destructured_parameter_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_destructured_parameter_child(this, i, result) }
@@ -544,7 +543,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Do" }
/** Gets the location of this element. */
override Location getLocation() { ruby_do_def(this, result) }
override L::Location getLocation() { ruby_do_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_do_child(this, i, result) }
@@ -559,7 +558,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "DoBlock" }
/** Gets the location of this element. */
override Location getLocation() { ruby_do_block_def(this, result) }
override L::Location getLocation() { ruby_do_block_def(this, result) }
/** Gets the node corresponding to the field `parameters`. */
BlockParameters getParameters() { ruby_do_block_parameters(this, result) }
@@ -579,7 +578,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "ElementReference" }
/** Gets the location of this element. */
override Location getLocation() { ruby_element_reference_def(this, _, result) }
override L::Location getLocation() { ruby_element_reference_def(this, _, result) }
/** Gets the node corresponding to the field `object`. */
UnderscorePrimary getObject() { ruby_element_reference_def(this, result, _) }
@@ -599,7 +598,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Else" }
/** Gets the location of this element. */
override Location getLocation() { ruby_else_def(this, result) }
override L::Location getLocation() { ruby_else_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_else_child(this, i, result) }
@@ -614,7 +613,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Elsif" }
/** Gets the location of this element. */
override Location getLocation() { ruby_elsif_def(this, _, result) }
override L::Location getLocation() { ruby_elsif_def(this, _, result) }
/** Gets the node corresponding to the field `alternative`. */
AstNode getAlternative() { ruby_elsif_alternative(this, result) }
@@ -645,7 +644,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "EndBlock" }
/** Gets the location of this element. */
override Location getLocation() { ruby_end_block_def(this, result) }
override L::Location getLocation() { ruby_end_block_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_end_block_child(this, i, result) }
@@ -660,7 +659,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Ensure" }
/** Gets the location of this element. */
override Location getLocation() { ruby_ensure_def(this, result) }
override L::Location getLocation() { ruby_ensure_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_ensure_child(this, i, result) }
@@ -681,7 +680,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "ExceptionVariable" }
/** Gets the location of this element. */
override Location getLocation() { ruby_exception_variable_def(this, _, result) }
override L::Location getLocation() { ruby_exception_variable_def(this, _, result) }
/** Gets the child of this node. */
UnderscoreLhs getChild() { ruby_exception_variable_def(this, result, _) }
@@ -696,7 +695,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Exceptions" }
/** Gets the location of this element. */
override Location getLocation() { ruby_exceptions_def(this, result) }
override L::Location getLocation() { ruby_exceptions_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_exceptions_child(this, i, result) }
@@ -723,7 +722,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "For" }
/** Gets the location of this element. */
override Location getLocation() { ruby_for_def(this, _, _, _, result) }
override L::Location getLocation() { ruby_for_def(this, _, _, _, result) }
/** Gets the node corresponding to the field `body`. */
Do getBody() { ruby_for_def(this, result, _, _, _) }
@@ -766,7 +765,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Hash" }
/** Gets the location of this element. */
override Location getLocation() { ruby_hash_def(this, result) }
override L::Location getLocation() { ruby_hash_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_hash_child(this, i, result) }
@@ -787,7 +786,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "HashSplatArgument" }
/** Gets the location of this element. */
override Location getLocation() { ruby_hash_splat_argument_def(this, _, result) }
override L::Location getLocation() { ruby_hash_splat_argument_def(this, _, result) }
/** Gets the child of this node. */
UnderscoreArg getChild() { ruby_hash_splat_argument_def(this, result, _) }
@@ -802,7 +801,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "HashSplatParameter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_hash_splat_parameter_def(this, result) }
override L::Location getLocation() { ruby_hash_splat_parameter_def(this, result) }
/** Gets the node corresponding to the field `name`. */
Identifier getName() { ruby_hash_splat_parameter_name(this, result) }
@@ -823,7 +822,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "HeredocBody" }
/** Gets the location of this element. */
override Location getLocation() { ruby_heredoc_body_def(this, result) }
override L::Location getLocation() { ruby_heredoc_body_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_heredoc_body_child(this, i, result) }
@@ -856,7 +855,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "If" }
/** Gets the location of this element. */
override Location getLocation() { ruby_if_def(this, _, result) }
override L::Location getLocation() { ruby_if_def(this, _, result) }
/** Gets the node corresponding to the field `alternative`. */
AstNode getAlternative() { ruby_if_alternative(this, result) }
@@ -881,7 +880,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "IfModifier" }
/** Gets the location of this element. */
override Location getLocation() { ruby_if_modifier_def(this, _, _, result) }
override L::Location getLocation() { ruby_if_modifier_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
UnderscoreStatement getBody() { ruby_if_modifier_def(this, result, _, _) }
@@ -901,7 +900,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "In" }
/** Gets the location of this element. */
override Location getLocation() { ruby_in_def(this, _, result) }
override L::Location getLocation() { ruby_in_def(this, _, result) }
/** Gets the child of this node. */
UnderscoreArg getChild() { ruby_in_def(this, result, _) }
@@ -928,7 +927,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Interpolation" }
/** Gets the location of this element. */
override Location getLocation() { ruby_interpolation_def(this, result) }
override L::Location getLocation() { ruby_interpolation_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_interpolation_child(this, i, result) }
@@ -943,7 +942,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "KeywordParameter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_keyword_parameter_def(this, _, result) }
override L::Location getLocation() { ruby_keyword_parameter_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
Identifier getName() { ruby_keyword_parameter_def(this, result, _) }
@@ -963,7 +962,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Lambda" }
/** Gets the location of this element. */
override Location getLocation() { ruby_lambda_def(this, _, result) }
override L::Location getLocation() { ruby_lambda_def(this, _, result) }
/** Gets the node corresponding to the field `body`. */
AstNode getBody() { ruby_lambda_def(this, result, _) }
@@ -983,7 +982,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "LambdaParameters" }
/** Gets the location of this element. */
override Location getLocation() { ruby_lambda_parameters_def(this, result) }
override L::Location getLocation() { ruby_lambda_parameters_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_lambda_parameters_child(this, i, result) }
@@ -998,7 +997,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "LeftAssignmentList" }
/** Gets the location of this element. */
override Location getLocation() { ruby_left_assignment_list_def(this, result) }
override L::Location getLocation() { ruby_left_assignment_list_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_left_assignment_list_child(this, i, result) }
@@ -1013,7 +1012,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Method" }
/** Gets the location of this element. */
override Location getLocation() { ruby_method_def(this, _, result) }
override L::Location getLocation() { ruby_method_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
UnderscoreMethodName getName() { ruby_method_def(this, result, _) }
@@ -1038,7 +1037,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "MethodParameters" }
/** Gets the location of this element. */
override Location getLocation() { ruby_method_parameters_def(this, result) }
override L::Location getLocation() { ruby_method_parameters_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_method_parameters_child(this, i, result) }
@@ -1053,7 +1052,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Module" }
/** Gets the location of this element. */
override Location getLocation() { ruby_module_def(this, _, result) }
override L::Location getLocation() { ruby_module_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
AstNode getName() { ruby_module_def(this, result, _) }
@@ -1073,7 +1072,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Next" }
/** Gets the location of this element. */
override Location getLocation() { ruby_next_def(this, result) }
override L::Location getLocation() { ruby_next_def(this, result) }
/** Gets the child of this node. */
ArgumentList getChild() { ruby_next_child(this, result) }
@@ -1100,7 +1099,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "OperatorAssignment" }
/** Gets the location of this element. */
override Location getLocation() { ruby_operator_assignment_def(this, _, _, _, result) }
override L::Location getLocation() { ruby_operator_assignment_def(this, _, _, _, result) }
/** Gets the node corresponding to the field `left`. */
UnderscoreLhs getLeft() { ruby_operator_assignment_def(this, result, _, _, _) }
@@ -1152,7 +1151,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "OptionalParameter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_optional_parameter_def(this, _, _, result) }
override L::Location getLocation() { ruby_optional_parameter_def(this, _, _, result) }
/** Gets the node corresponding to the field `name`. */
Identifier getName() { ruby_optional_parameter_def(this, result, _, _) }
@@ -1173,7 +1172,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Pair" }
/** Gets the location of this element. */
override Location getLocation() { ruby_pair_def(this, _, _, result) }
override L::Location getLocation() { ruby_pair_def(this, _, _, result) }
/** Gets the node corresponding to the field `key`. */
AstNode getKey() { ruby_pair_def(this, result, _, _) }
@@ -1193,7 +1192,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "ParenthesizedStatements" }
/** Gets the location of this element. */
override Location getLocation() { ruby_parenthesized_statements_def(this, result) }
override L::Location getLocation() { ruby_parenthesized_statements_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_parenthesized_statements_child(this, i, result) }
@@ -1208,7 +1207,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Pattern" }
/** Gets the location of this element. */
override Location getLocation() { ruby_pattern_def(this, _, result) }
override L::Location getLocation() { ruby_pattern_def(this, _, result) }
/** Gets the child of this node. */
AstNode getChild() { ruby_pattern_def(this, result, _) }
@@ -1223,7 +1222,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Program" }
/** Gets the location of this element. */
override Location getLocation() { ruby_program_def(this, result) }
override L::Location getLocation() { ruby_program_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_program_child(this, i, result) }
@@ -1238,7 +1237,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Range" }
/** Gets the location of this element. */
override Location getLocation() { ruby_range_def(this, _, result) }
override L::Location getLocation() { ruby_range_def(this, _, result) }
/** Gets the node corresponding to the field `begin`. */
UnderscoreArg getBegin() { ruby_range_begin(this, result) }
@@ -1267,7 +1266,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Rational" }
/** Gets the location of this element. */
override Location getLocation() { ruby_rational_def(this, _, result) }
override L::Location getLocation() { ruby_rational_def(this, _, result) }
/** Gets the child of this node. */
AstNode getChild() { ruby_rational_def(this, result, _) }
@@ -1282,7 +1281,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Redo" }
/** Gets the location of this element. */
override Location getLocation() { ruby_redo_def(this, result) }
override L::Location getLocation() { ruby_redo_def(this, result) }
/** Gets the child of this node. */
ArgumentList getChild() { ruby_redo_child(this, result) }
@@ -1297,7 +1296,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Regex" }
/** Gets the location of this element. */
override Location getLocation() { ruby_regex_def(this, result) }
override L::Location getLocation() { ruby_regex_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_regex_child(this, i, result) }
@@ -1312,7 +1311,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Rescue" }
/** Gets the location of this element. */
override Location getLocation() { ruby_rescue_def(this, result) }
override L::Location getLocation() { ruby_rescue_def(this, result) }
/** Gets the node corresponding to the field `body`. */
Then getBody() { ruby_rescue_body(this, result) }
@@ -1337,7 +1336,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "RescueModifier" }
/** Gets the location of this element. */
override Location getLocation() { ruby_rescue_modifier_def(this, _, _, result) }
override L::Location getLocation() { ruby_rescue_modifier_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
UnderscoreStatement getBody() { ruby_rescue_modifier_def(this, result, _, _) }
@@ -1357,7 +1356,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "RestAssignment" }
/** Gets the location of this element. */
override Location getLocation() { ruby_rest_assignment_def(this, result) }
override L::Location getLocation() { ruby_rest_assignment_def(this, result) }
/** Gets the child of this node. */
UnderscoreLhs getChild() { ruby_rest_assignment_child(this, result) }
@@ -1372,7 +1371,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Retry" }
/** Gets the location of this element. */
override Location getLocation() { ruby_retry_def(this, result) }
override L::Location getLocation() { ruby_retry_def(this, result) }
/** Gets the child of this node. */
ArgumentList getChild() { ruby_retry_child(this, result) }
@@ -1387,7 +1386,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Return" }
/** Gets the location of this element. */
override Location getLocation() { ruby_return_def(this, result) }
override L::Location getLocation() { ruby_return_def(this, result) }
/** Gets the child of this node. */
ArgumentList getChild() { ruby_return_child(this, result) }
@@ -1402,7 +1401,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "RightAssignmentList" }
/** Gets the location of this element. */
override Location getLocation() { ruby_right_assignment_list_def(this, result) }
override L::Location getLocation() { ruby_right_assignment_list_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_right_assignment_list_child(this, i, result) }
@@ -1417,7 +1416,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "ScopeResolution" }
/** Gets the location of this element. */
override Location getLocation() { ruby_scope_resolution_def(this, _, result) }
override L::Location getLocation() { ruby_scope_resolution_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
AstNode getName() { ruby_scope_resolution_def(this, result, _) }
@@ -1443,7 +1442,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Setter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_setter_def(this, _, result) }
override L::Location getLocation() { ruby_setter_def(this, _, result) }
/** Gets the node corresponding to the field `name`. */
Identifier getName() { ruby_setter_def(this, result, _) }
@@ -1464,7 +1463,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "SingletonClass" }
/** Gets the location of this element. */
override Location getLocation() { ruby_singleton_class_def(this, _, result) }
override L::Location getLocation() { ruby_singleton_class_def(this, _, result) }
/** Gets the node corresponding to the field `value`. */
UnderscoreArg getValue() { ruby_singleton_class_def(this, result, _) }
@@ -1484,7 +1483,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "SingletonMethod" }
/** Gets the location of this element. */
override Location getLocation() { ruby_singleton_method_def(this, _, _, result) }
override L::Location getLocation() { ruby_singleton_method_def(this, _, _, result) }
/** Gets the node corresponding to the field `name`. */
UnderscoreMethodName getName() { ruby_singleton_method_def(this, result, _, _) }
@@ -1513,7 +1512,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "SplatArgument" }
/** Gets the location of this element. */
override Location getLocation() { ruby_splat_argument_def(this, _, result) }
override L::Location getLocation() { ruby_splat_argument_def(this, _, result) }
/** Gets the child of this node. */
UnderscoreArg getChild() { ruby_splat_argument_def(this, result, _) }
@@ -1528,7 +1527,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "SplatParameter" }
/** Gets the location of this element. */
override Location getLocation() { ruby_splat_parameter_def(this, result) }
override L::Location getLocation() { ruby_splat_parameter_def(this, result) }
/** Gets the node corresponding to the field `name`. */
Identifier getName() { ruby_splat_parameter_name(this, result) }
@@ -1543,7 +1542,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "String" }
/** Gets the location of this element. */
override Location getLocation() { ruby_string_def(this, result) }
override L::Location getLocation() { ruby_string_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_string_child(this, i, result) }
@@ -1558,7 +1557,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "StringArray" }
/** Gets the location of this element. */
override Location getLocation() { ruby_string_array_def(this, result) }
override L::Location getLocation() { ruby_string_array_def(this, result) }
/** Gets the `i`th child of this node. */
BareString getChild(int i) { ruby_string_array_child(this, i, result) }
@@ -1579,7 +1578,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Subshell" }
/** Gets the location of this element. */
override Location getLocation() { ruby_subshell_def(this, result) }
override L::Location getLocation() { ruby_subshell_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_subshell_child(this, i, result) }
@@ -1600,7 +1599,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Superclass" }
/** Gets the location of this element. */
override Location getLocation() { ruby_superclass_def(this, _, result) }
override L::Location getLocation() { ruby_superclass_def(this, _, result) }
/** Gets the child of this node. */
AstNode getChild() { ruby_superclass_def(this, result, _) }
@@ -1615,7 +1614,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "SymbolArray" }
/** Gets the location of this element. */
override Location getLocation() { ruby_symbol_array_def(this, result) }
override L::Location getLocation() { ruby_symbol_array_def(this, result) }
/** Gets the `i`th child of this node. */
BareSymbol getChild(int i) { ruby_symbol_array_child(this, i, result) }
@@ -1630,7 +1629,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Then" }
/** Gets the location of this element. */
override Location getLocation() { ruby_then_def(this, result) }
override L::Location getLocation() { ruby_then_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { ruby_then_child(this, i, result) }
@@ -1651,7 +1650,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Unary" }
/** Gets the location of this element. */
override Location getLocation() { ruby_unary_def(this, _, _, result) }
override L::Location getLocation() { ruby_unary_def(this, _, _, result) }
/** Gets the node corresponding to the field `operand`. */
AstNode getOperand() { ruby_unary_def(this, result, _, _) }
@@ -1683,7 +1682,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Undef" }
/** Gets the location of this element. */
override Location getLocation() { ruby_undef_def(this, result) }
override L::Location getLocation() { ruby_undef_def(this, result) }
/** Gets the `i`th child of this node. */
UnderscoreMethodName getChild(int i) { ruby_undef_child(this, i, result) }
@@ -1704,7 +1703,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Unless" }
/** Gets the location of this element. */
override Location getLocation() { ruby_unless_def(this, _, result) }
override L::Location getLocation() { ruby_unless_def(this, _, result) }
/** Gets the node corresponding to the field `alternative`. */
AstNode getAlternative() { ruby_unless_alternative(this, result) }
@@ -1729,7 +1728,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "UnlessModifier" }
/** Gets the location of this element. */
override Location getLocation() { ruby_unless_modifier_def(this, _, _, result) }
override L::Location getLocation() { ruby_unless_modifier_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
UnderscoreStatement getBody() { ruby_unless_modifier_def(this, result, _, _) }
@@ -1749,7 +1748,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Until" }
/** Gets the location of this element. */
override Location getLocation() { ruby_until_def(this, _, _, result) }
override L::Location getLocation() { ruby_until_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
Do getBody() { ruby_until_def(this, result, _, _) }
@@ -1769,7 +1768,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "UntilModifier" }
/** Gets the location of this element. */
override Location getLocation() { ruby_until_modifier_def(this, _, _, result) }
override L::Location getLocation() { ruby_until_modifier_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
UnderscoreStatement getBody() { ruby_until_modifier_def(this, result, _, _) }
@@ -1789,7 +1788,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "When" }
/** Gets the location of this element. */
override Location getLocation() { ruby_when_def(this, result) }
override L::Location getLocation() { ruby_when_def(this, result) }
/** Gets the node corresponding to the field `body`. */
Then getBody() { ruby_when_body(this, result) }
@@ -1809,7 +1808,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "While" }
/** Gets the location of this element. */
override Location getLocation() { ruby_while_def(this, _, _, result) }
override L::Location getLocation() { ruby_while_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
Do getBody() { ruby_while_def(this, result, _, _) }
@@ -1829,7 +1828,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "WhileModifier" }
/** Gets the location of this element. */
override Location getLocation() { ruby_while_modifier_def(this, _, _, result) }
override L::Location getLocation() { ruby_while_modifier_def(this, _, _, result) }
/** Gets the node corresponding to the field `body`. */
UnderscoreStatement getBody() { ruby_while_modifier_def(this, result, _, _) }
@@ -1849,7 +1848,7 @@ module Ruby {
override string getAPrimaryQlClass() { result = "Yield" }
/** Gets the location of this element. */
override Location getLocation() { ruby_yield_def(this, result) }
override L::Location getLocation() { ruby_yield_def(this, result) }
/** Gets the child of this node. */
ArgumentList getChild() { ruby_yield_child(this, result) }
@@ -1866,7 +1865,7 @@ module Erb {
string toString() { result = this.getAPrimaryQlClass() }
/** Gets the location of this element. */
Location getLocation() { none() }
L::Location getLocation() { none() }
/** Gets the parent of this element. */
AstNode getParent() { erb_ast_node_parent(this, result, _) }
@@ -1890,7 +1889,7 @@ module Erb {
string getValue() { erb_tokeninfo(this, _, result, _) }
/** Gets the location of this token. */
override Location getLocation() { erb_tokeninfo(this, _, _, result) }
override L::Location getLocation() { erb_tokeninfo(this, _, _, result) }
/** Gets a string representation of this element. */
override string toString() { result = this.getValue() }
@@ -1923,7 +1922,7 @@ module Erb {
override string getAPrimaryQlClass() { result = "CommentDirective" }
/** Gets the location of this element. */
override Location getLocation() { erb_comment_directive_def(this, _, result) }
override L::Location getLocation() { erb_comment_directive_def(this, _, result) }
/** Gets the child of this node. */
Comment getChild() { erb_comment_directive_def(this, result, _) }
@@ -1944,7 +1943,7 @@ module Erb {
override string getAPrimaryQlClass() { result = "Directive" }
/** Gets the location of this element. */
override Location getLocation() { erb_directive_def(this, _, result) }
override L::Location getLocation() { erb_directive_def(this, _, result) }
/** Gets the child of this node. */
Code getChild() { erb_directive_def(this, result, _) }
@@ -1959,7 +1958,7 @@ module Erb {
override string getAPrimaryQlClass() { result = "GraphqlDirective" }
/** Gets the location of this element. */
override Location getLocation() { erb_graphql_directive_def(this, _, result) }
override L::Location getLocation() { erb_graphql_directive_def(this, _, result) }
/** Gets the child of this node. */
Code getChild() { erb_graphql_directive_def(this, result, _) }
@@ -1974,7 +1973,7 @@ module Erb {
override string getAPrimaryQlClass() { result = "OutputDirective" }
/** Gets the location of this element. */
override Location getLocation() { erb_output_directive_def(this, _, result) }
override L::Location getLocation() { erb_output_directive_def(this, _, result) }
/** Gets the child of this node. */
Code getChild() { erb_output_directive_def(this, result, _) }
@@ -1989,7 +1988,7 @@ module Erb {
override string getAPrimaryQlClass() { result = "Template" }
/** Gets the location of this element. */
override Location getLocation() { erb_template_def(this, result) }
override L::Location getLocation() { erb_template_def(this, result) }
/** Gets the `i`th child of this node. */
AstNode getChild(int i) { erb_template_child(this, i, result) }

View File

@@ -495,7 +495,7 @@ abstract class VariableAccessImpl extends Expr, TVariableAccess {
}
module LocalVariableAccess {
predicate range(Ruby::Identifier id, LocalVariable v) {
predicate range(Ruby::Identifier id, TLocalVariableReal v) {
access(id, v) and
(
explicitWriteAccess(id, _)

View File

@@ -320,7 +320,11 @@ module ExprNodes {
/** Gets the the keyword argument whose key is `keyword` of this call. */
final ExprCfgNode getKeywordArgument(string keyword) {
e.hasCfgChild(e.getKeywordArgument(keyword), this, result)
exists(PairCfgNode n |
e.hasCfgChild(e.getAnArgument(), this, n) and
n.getKey().getExpr().(SymbolLiteral).getValueText() = keyword and
result = n.getValue()
)
}
/** Gets the number of arguments of this call. */
@@ -426,6 +430,27 @@ module ExprNodes {
ParenthesizedExprCfgNode() { this.getExpr() instanceof ParenthesizedExpr }
}
private class PairChildMapping extends ExprChildMapping, Pair {
override predicate relevantChild(Expr e) { e = this.getKey() or e = this.getValue() }
}
/** A control-flow node that wraps a `Pair` AST expression. */
class PairCfgNode extends ExprCfgNode {
override PairChildMapping e;
final override Pair getExpr() { result = ExprCfgNode.super.getExpr() }
/**
* Gets the key expression of this pair.
*/
final ExprCfgNode getKey() { e.hasCfgChild(e.getKey(), this, result) }
/**
* Gets the value expression of this pair.
*/
final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
}
/** A control-flow node that wraps a `VariableReadAccess` AST expression. */
class VariableReadAccessCfgNode extends ExprCfgNode {
override VariableReadAccess e;

View File

@@ -608,6 +608,8 @@ module Trees {
}
}
private class ForwardParameterTree extends LeafTree, ForwardParameter { }
private class ForInTree extends LeafTree, ForIn { }
/**

View File

@@ -924,7 +924,8 @@ module Consistency {
succSplits(pred, predSplits, succ, succSplits, c) and
split.hasEntry(pred, succ, c) and
not split.getKind() = predSplits.getASplit().getKind() and
not split = succSplits.getASplit()
not split = succSplits.getASplit() and
split.getKind().isEnabled(succ)
}
query predicate breakInvariant5(
@@ -942,4 +943,9 @@ module Consistency {
strictcount(getASuccessor(node, t)) > 1 and
successor = getASuccessor(node, t)
}
query predicate deadEnd(Node node) {
not node instanceof TExitNode and
not exists(getASuccessor(node, _))
}
}

View File

@@ -0,0 +1,16 @@
/** Provides commonly used dataflow sanitizers */
private import codeql.ruby.AST
private import codeql.ruby.DataFlow
/**
* A sanitizer for flow into a string interpolation component,
* provided that component does not form a prefix of the string.
*
* This is useful for URLs and paths, where the fixed prefix prevents the user from controlling the target.
*/
class PrefixedStringInterpolation extends DataFlow::Node {
PrefixedStringInterpolation() {
exists(StringlikeLiteral str, int n | str.getComponent(n) = this.asExpr().getExpr() and n > 0)
}
}

View File

@@ -450,7 +450,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
*/
predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) {
exists(ReturningNode r |
r.getEnclosingCallable().asCallable() = c and
nodeGetEnclosingCallable(r).asCallable() = c and
(
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
r.(ExprReturnNode) = e

View File

@@ -3740,13 +3740,14 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
exists(Configuration config |
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
not arg.isHidden()
)
}
@@ -3780,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
apout = ret.getAp() and
not ret.isHidden()
apout = ret.getAp()
)
}
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
n.getASuccessorImpl() = result and
result.isHidden() and
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
localFlowBigStep(n1, n2, _, _, _, _) or
store(n1, _, n2, _, _) or
read(n1, _, n2, _)
)
}
@@ -3790,11 +3800,12 @@ 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, PathNodeMid ret, PathNodeMid out) {
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
subpaths03(arg, p, ret, o, apout) and
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout

View File

@@ -3740,13 +3740,14 @@ private module Subpaths {
*/
pragma[nomagic]
private predicate subpaths01(
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
NodeEx out, AccessPath apout
) {
exists(Configuration config |
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
not arg.isHidden()
)
}
@@ -3780,8 +3781,17 @@ private module Subpaths {
innercc = ret.getCallContext() and
sc = ret.getSummaryCtx() and
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
apout = ret.getAp() and
not ret.isHidden()
apout = ret.getAp()
)
}
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
n.getASuccessorImpl() = result and
result.isHidden() and
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
localFlowBigStep(n1, n2, _, _, _, _) or
store(n1, _, n2, _, _) or
read(n1, _, n2, _)
)
}
@@ -3790,11 +3800,12 @@ 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, PathNodeMid ret, PathNodeMid out) {
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
pragma[only_bind_into](arg).getASuccessor() = par and
pragma[only_bind_into](arg).getASuccessor() = out and
subpaths03(arg, p, ret, o, apout) and
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
not ret.isHidden() and
par.getNodeEx() = p and
out.getNodeEx() = o and
out.getAp() = apout

View File

@@ -7,12 +7,16 @@ private import SsaImpl as SsaImpl
private import FlowSummaryImpl as FlowSummaryImpl
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, int pos) {
p.isParameterOf(c, pos)
}
abstract class NodeImpl extends Node {
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.getCfgScope()) }
/** Do not call: use `getEnclosingCallable()` instead. */
abstract CfgScope getCfgScope();
@@ -312,7 +316,7 @@ private module ParameterNodes {
abstract class ParameterNodeImpl extends ParameterNode, NodeImpl {
abstract predicate isSourceParameterOf(Callable c, int i);
override predicate isParameterOf(DataFlowCallable c, int i) {
predicate isParameterOf(DataFlowCallable c, int i) {
this.isSourceParameterOf(c.asCallable(), i)
}
}
@@ -802,7 +806,7 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
)
or
receiver = call.(SummaryCall).getReceiver() and
if receiver.(ParameterNode).isParameterOf(_, -2)
if receiver.(ParameterNodeImpl).isParameterOf(_, -2)
then kind = TYieldCallKind()
else kind = TLambdaCallKind()
}

View File

@@ -25,8 +25,6 @@ class Node extends TNode {
// TODO: cache
final Location getLocation() { result = this.(NodeImpl).getLocationImpl() }
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.(NodeImpl).getCfgScope()) }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
@@ -60,6 +58,9 @@ class CallNode extends LocalSourceNode {
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
Node getKeywordArgument(string name) { result.asExpr() = node.getKeywordArgument(name) }
/** Gets the name of the the method called by the method call (if any) corresponding to this data-flow node */
string getMethodName() { result = node.getExpr().(MethodCall).getMethodName() }
}
/**
@@ -85,12 +86,6 @@ class ExprNode extends Node, TExprNode {
class ParameterNode extends Node, TParameterNode {
/** Gets the parameter corresponding to this node, if any. */
Parameter getParameter() { none() }
/**
* Holds if this node is the parameter of callable `c` at the specified
* (zero-based) position.
*/
predicate isParameterOf(DataFlowCallable c, int i) { none() }
}
/**
@@ -110,6 +105,14 @@ class LocalSourceNode extends Node {
*/
pragma[inline]
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
/**
* Gets a node that may flow into this one using one heap and/or interprocedural step.
*
* See `TypeBackTracker` for more details about how to use this.
*/
pragma[inline]
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
}
predicate hasLocalSource(Node sink, Node source) {

View File

@@ -85,6 +85,9 @@ module Public {
/** Holds if this stack contains summary component `c`. */
predicate contains(SummaryComponent c) { c = this.drop(_).head() }
/** Gets the bottom element of this stack. */
SummaryComponent bottom() { result = this.drop(this.length() - 1).head() }
/** Gets a textual representation of this stack. */
string toString() {
exists(SummaryComponent head, SummaryComponentStack tail |
@@ -197,6 +200,8 @@ module Private {
or
tail.(RequiredSummaryComponentStack).required(TParameterSummaryComponent(_)) and
head = thisParam()
or
derivedFluentFlowPush(_, _, _, head, tail, _)
}
pragma[nomagic]
@@ -210,7 +215,7 @@ module Private {
c.propagatesFlow(output, input, preservesValue) and
preservesValue = true and
isCallbackParameter(input) and
isContentOfArgument(output)
isContentOfArgument(output, _)
or
// flow from the receiver of a callback into the instance-parameter
exists(SummaryComponentStack s, SummaryComponentStack callbackRef |
@@ -222,16 +227,81 @@ module Private {
output = TConsSummaryComponentStack(thisParam(), input) and
preservesValue = true
)
or
exists(SummaryComponentStack arg, SummaryComponentStack return |
derivedFluentFlow(c, input, arg, return, preservesValue)
|
arg.length() = 1 and
output = return
or
exists(SummaryComponent head, SummaryComponentStack tail |
derivedFluentFlowPush(c, input, arg, head, tail, 0) and
output = SummaryComponentStack::push(head, tail)
)
)
or
// Chain together summaries where values get passed into callbacks along the way
exists(SummaryComponentStack mid, boolean preservesValue1, boolean preservesValue2 |
c.propagatesFlow(input, mid, preservesValue1) and
c.propagatesFlow(mid, output, preservesValue2) and
mid.drop(mid.length() - 2) =
SummaryComponentStack::push(TParameterSummaryComponent(_),
SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and
preservesValue = preservesValue1.booleanAnd(preservesValue2)
)
}
/**
* Holds if `c` has a flow summary from `input` to `arg`, where `arg`
* writes to (contents of) the `i`th argument, and `c` has a
* value-preserving flow summary from the `i`th argument to a return value
* (`return`).
*
* In such a case, we derive flow from `input` to (contents of) the return
* value.
*
* As an example, this simplifies modeling of fluent methods:
* for `StringBuilder.append(x)` with a specified value flow from qualifier to
* return value and taint flow from argument 0 to the qualifier, then this
* allows us to infer taint flow from argument 0 to the return value.
*/
pragma[nomagic]
private predicate derivedFluentFlow(
SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg,
SummaryComponentStack return, boolean preservesValue
) {
exists(int i |
summary(c, input, arg, preservesValue) and
isContentOfArgument(arg, i) and
summary(c, SummaryComponentStack::singleton(TArgumentSummaryComponent(i)), return, true) and
return.bottom() = TReturnSummaryComponent(_)
)
}
pragma[nomagic]
private predicate derivedFluentFlowPush(
SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg,
SummaryComponent head, SummaryComponentStack tail, int i
) {
derivedFluentFlow(c, input, arg, tail, _) and
head = arg.drop(i).head() and
i = arg.length() - 2
or
exists(SummaryComponent head0, SummaryComponentStack tail0 |
derivedFluentFlowPush(c, input, arg, head0, tail0, i + 1) and
head = arg.drop(i).head() and
tail = SummaryComponentStack::push(head0, tail0)
)
}
private predicate isCallbackParameter(SummaryComponentStack s) {
s.head() = TParameterSummaryComponent(_) and exists(s.tail())
}
private predicate isContentOfArgument(SummaryComponentStack s) {
s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail())
private predicate isContentOfArgument(SummaryComponentStack s, int i) {
s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), i)
or
s = TSingletonSummaryComponentStack(TArgumentSummaryComponent(_))
s = TSingletonSummaryComponentStack(TArgumentSummaryComponent(i))
}
private predicate outputState(SummarizedCallable c, SummaryComponentStack s) {
@@ -508,9 +578,14 @@ module Private {
* node, and back out to `p`.
*/
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
exists(SummarizedCallable c, int i |
c.clearsContent(i, _) and
p.isParameterOf(c, i)
exists(SummarizedCallable c, int i | p.isParameterOf(c, i) |
c.clearsContent(i, _)
or
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
summary(c, inputContents, outputContents, _) and
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(i)) and
outputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(i))
)
)
}
@@ -534,22 +609,6 @@ module Private {
preservesValue = false and not summary(c, inputContents, outputContents, true)
)
or
// If flow through a method updates a parameter from some input A, and that
// parameter also is returned through B, then we'd like a combined flow from A
// to B as well. As an example, this simplifies modeling of fluent methods:
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
// return value and taint flow from argument 0 to the qualifier, then this
// allows us to infer taint flow from argument 0 to the return value.
succ instanceof ParamNode and
summaryPostUpdateNode(pred, succ) and
preservesValue = true
or
// Similarly we would like to chain together summaries where values get passed
// into callbacks along the way.
pred instanceof ArgNode and
summaryPostUpdateNode(succ, pred) and
preservesValue = true
or
exists(SummarizedCallable c, int i |
pred.(ParamNode).isParameterOf(c, i) and
succ = summaryNode(c, TSummaryNodeClearsContentState(i, _)) and

View File

@@ -141,25 +141,23 @@ private module Liveness {
private import Liveness
/** Holds if `bb1` strictly dominates `bb2`. */
private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) {
bb1 = getImmediateBasicBlockDominator+(bb2)
}
/** Holds if `bb1` dominates a predecessor of `bb2`. */
private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) {
exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) |
bb1 = pred
or
strictlyDominates(bb1, pred)
)
}
/** Holds if `df` is in the dominance frontier of `bb`. */
pragma[noinline]
/**
* Holds if `df` is in the dominance frontier of `bb`.
*
* This is equivalent to:
*
* ```ql
* bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
* not bb = getImmediateBasicBlockDominator+(df)
* ```
*/
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
dominatesPredecessor(bb, df) and
not strictlyDominates(bb, df)
bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
or
exists(BasicBlock prev | inDominanceFrontier(prev, df) |
bb = getImmediateBasicBlockDominator(prev) and
not bb = getImmediateBasicBlockDominator(df)
)
}
/**
@@ -636,3 +634,28 @@ class UncertainWriteDefinition extends WriteDefinition {
)
}
}
/** Provides a set of consistency queries. */
module Consistency {
abstract class RelevantDefinition extends Definition {
abstract predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
);
}
query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) {
ssaDefReachesRead(v, def, bb, i) and
not exists(unique(Definition def0 | ssaDefReachesRead(_, def0, bb, i)))
}
query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) {
variableRead(bb, i, v, _) and
not ssaDefReachesRead(_, _, bb, i)
}
query predicate deadDef(RelevantDefinition def, SourceVariable v) {
v = def.getSourceVariable() and
not ssaDefReachesRead(_, def, _, _) and
not phiHasInputFromBlock(_, def, _)
}
}

View File

@@ -4,16 +4,16 @@ private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
/** Defines calls to `ActiveStorage::Filename#sanitized` as path sanitizers. */
/** A call to `ActiveStorage::Filename#sanitized`, considered as a path sanitizer. */
class ActiveStorageFilenameSanitizedCall extends Path::PathSanitization::Range, DataFlow::CallNode {
ActiveStorageFilenameSanitizedCall() {
this.getReceiver() =
API::getTopLevelMember("ActiveStorage").getMember("Filename").getAnInstantiation() and
this.asExpr().getExpr().(MethodCall).getMethodName() = "sanitized"
this.getMethodName() = "sanitized"
}
}
/** Taint summary for `ActiveStorage::Filename.new`. */
/** The taint summary for `ActiveStorage::Filename.new`. */
class ActiveStorageFilenameNewSummary extends SummarizedCallable {
ActiveStorageFilenameNewSummary() { this = "ActiveStorage::Filename.new" }
@@ -33,7 +33,7 @@ class ActiveStorageFilenameNewSummary extends SummarizedCallable {
}
}
/** Taint summary for `ActiveStorage::Filename#sanitized`. */
/** The taint summary for `ActiveStorage::Filename#sanitized`. */
class ActiveStorageFilenameSanitizedSummary extends SummarizedCallable {
ActiveStorageFilenameSanitizedSummary() { this = "ActiveStorage::Filename#sanitized" }

View File

@@ -127,8 +127,7 @@ module IO {
api = "IO" and
exists(IOInstanceStrict ii |
this.getReceiver() = ii and
this.asExpr().getExpr().(MethodCall).getMethodName() =
ioFileReaderMethodName(classMethodCall)
this.getMethodName() = ioFileReaderMethodName(classMethodCall)
)
or
// File instance methods
@@ -136,8 +135,7 @@ module IO {
api = "File" and
exists(File::FileInstance fi |
this.getReceiver() = fi and
this.asExpr().getExpr().(MethodCall).getMethodName() =
ioFileReaderMethodName(classMethodCall)
this.getMethodName() = ioFileReaderMethodName(classMethodCall)
)
// TODO: enumeration style methods such as `each`, `foreach`, etc.
}
@@ -232,7 +230,7 @@ module File {
// Instance methods
exists(FileInstance fi |
this.getReceiver() = fi and
this.asExpr().getExpr().(MethodCall).getMethodName() = ["path", "to_path"]
this.getMethodName() = ["path", "to_path"]
)
}
}
@@ -256,7 +254,7 @@ module File {
}
/**
* Flow summary for several methods on the `File` class that propagate taint
* A flow summary for several methods on the `File` class that propagate taint
* from their first argument to the return value.
*/
class FilePathConversionSummary extends SummarizedCallable {
@@ -279,7 +277,7 @@ module File {
}
/**
* Flow summary for `File.join`, which propagates taint from every argument to
* A flow summary for `File.join`, which propagates taint from every argument to
* its return value.
*/
class FileJoinSummary extends SummarizedCallable {

View File

@@ -27,8 +27,6 @@ class KernelMethodCall extends DataFlow::CallNode {
)
}
string getMethodName() { result = methodCall.getMethodName() }
int getNumberOfArguments() { result = methodCall.getNumberOfArguments() }
}
@@ -74,7 +72,7 @@ string basicObjectInstanceMethodName() {
}
/**
* Instance methods on `BasicObject`, which are available to all classes.
* An instance method on `BasicObject`, which is available to all classes.
*/
class BasicObjectInstanceMethodCall extends UnknownMethodCall {
BasicObjectInstanceMethodCall() { this.getMethodName() = basicObjectInstanceMethodName() }
@@ -95,14 +93,14 @@ string objectInstanceMethodName() {
}
/**
* Instance methods on `Object`, which are available to all classes except `BasicObject`.
* An instance method on `Object`, which is available to all classes except `BasicObject`.
*/
class ObjectInstanceMethodCall extends UnknownMethodCall {
ObjectInstanceMethodCall() { this.getMethodName() = objectInstanceMethodName() }
}
/**
* Method calls which have no known target.
* A `Method` call that has no known target.
* These will typically be calls to methods inherited from a superclass.
*/
class UnknownMethodCall extends MethodCall {

View File

@@ -18,12 +18,14 @@ private import codeql.ruby.ApiGraphs
* https://github.com/excon/excon/blob/master/README.md
*/
class ExconHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
API::Node requestNode;
API::Node connectionNode;
DataFlow::Node connectionUse;
ExconHttpRequest() {
requestUse = requestNode.getAnImmediateUse() and
connectionUse = connectionNode.getAnImmediateUse() and
connectionNode =
[
// one-off requests
@@ -44,6 +46,17 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
override DataFlow::Node getURL() {
// For one-off requests, the URL is in the first argument of the request method call.
// For connection re-use, the URL is split between the first argument of the `new` call
// and the `path` keyword argument of the request method call.
result = requestUse.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
or
result = requestUse.getKeywordArgument("path")
or
result = connectionUse.(DataFlow::CallNode).getArgument(0)
}
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
// Check for `ssl_verify_peer: false` in the options hash.
exists(DataFlow::Node arg, int i |

View File

@@ -11,12 +11,16 @@ private import codeql.ruby.ApiGraphs
* # connection re-use
* connection = Faraday.new("http://example.com")
* connection.get("/").body
*
* connection = Faraday.new(url: "http://example.com")
* connection.get("/").body
* ```
*/
class FaradayHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node requestUse;
API::Node requestNode;
API::Node connectionNode;
DataFlow::Node connectionUse;
DataFlow::CallNode requestUse;
FaradayHttpRequest() {
connectionNode =
@@ -29,11 +33,18 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
requestNode =
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
requestUse = requestNode.getAnImmediateUse() and
connectionUse = connectionNode.getAnImmediateUse() and
this = requestUse.asExpr().getExpr()
}
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
override DataFlow::Node getURL() {
result = requestUse.getArgument(0) or
result = connectionUse.(DataFlow::CallNode).getArgument(0) or
result = connectionUse.(DataFlow::CallNode).getKeywordArgument("url")
}
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
// `Faraday::new` takes an options hash as its second argument, and we're
// looking for

View File

@@ -12,7 +12,7 @@ private import codeql.ruby.ApiGraphs
class HttpClientRequest extends HTTP::Client::Request::Range {
API::Node requestNode;
API::Node connectionNode;
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
string method;
HttpClientRequest() {
@@ -31,6 +31,8 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
this = requestUse.asExpr().getExpr()
}
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
override DataFlow::Node getResponseBody() {
// The `get_content` and `post_content` methods return the response body as
// a string. The other methods return a `HTTPClient::Message` object which

View File

@@ -11,6 +11,7 @@ private import codeql.ruby.ApiGraphs
* # TODO: module inclusion
* class MyClass
* include HTTParty
* base_uri "http://example.com"
* end
*
* MyClass.new("http://example.com")
@@ -18,7 +19,7 @@ private import codeql.ruby.ApiGraphs
*/
class HttpartyRequest extends HTTP::Client::Request::Range {
API::Node requestNode;
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
HttpartyRequest() {
requestUse = requestNode.getAnImmediateUse() and
@@ -28,6 +29,8 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
this = requestUse.asExpr().getExpr()
}
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
override DataFlow::Node getResponseBody() {
// If HTTParty can recognise the response type, it will parse and return it
// directly from the request call. Otherwise, it will return a `HTTParty::Response`

View File

@@ -46,7 +46,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
* Gets the node representing the URL of the request.
* Currently unused, but may be useful in future, e.g. to filter out certain requests.
*/
DataFlow::Node getURLArgument() { result = request.getArgument(0) }
override DataFlow::Node getURL() { result = request.getArgument(0) }
override DataFlow::Node getResponseBody() { result = responseBody }

View File

@@ -14,7 +14,7 @@ private import codeql.ruby.frameworks.StandardLibrary
*/
class OpenUriRequest extends HTTP::Client::Request::Range {
API::Node requestNode;
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
OpenUriRequest() {
requestNode =
@@ -24,6 +24,8 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
this = requestUse.asExpr().getExpr()
}
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
override DataFlow::Node getResponseBody() {
result = requestNode.getAMethodCall(["read", "readlines"])
}
@@ -48,7 +50,7 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
* ```
*/
class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
OpenUriKernelOpenRequest() {
requestUse instanceof KernelMethodCall and
@@ -56,6 +58,8 @@ class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
this = requestUse.asExpr().getExpr()
}
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
override DataFlow::CallNode getResponseBody() {
result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
requestUse.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())

View File

@@ -6,23 +6,38 @@ private import codeql.ruby.ApiGraphs
* A call that makes an HTTP request using `RestClient`.
* ```ruby
* RestClient.get("http://example.com").body
* RestClient::Resource.new("http://example.com").get.body
* RestClient::Request.execute(url: "http://example.com").body
* ```
*/
class RestClientHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
API::Node requestNode;
API::Node connectionNode;
RestClientHttpRequest() {
connectionNode =
[
API::getTopLevelMember("RestClient"),
API::getTopLevelMember("RestClient").getMember("Resource").getInstance()
] and
requestNode =
connectionNode.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
requestUse = requestNode.getAnImmediateUse() and
this = requestUse.asExpr().getExpr()
this = requestUse.asExpr().getExpr() and
(
connectionNode =
[
API::getTopLevelMember("RestClient"),
API::getTopLevelMember("RestClient").getMember("Resource").getInstance()
] and
requestNode =
connectionNode.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
or
connectionNode = API::getTopLevelMember("RestClient").getMember("Request") and
requestNode = connectionNode.getReturn("execute")
)
}
override DataFlow::Node getURL() {
result = requestUse.getKeywordArgument("url")
or
result = requestUse.getArgument(0) and
// this rules out the alternative above
not result.asExpr().getExpr() instanceof Pair
}
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }

View File

@@ -9,7 +9,7 @@ private import codeql.ruby.ApiGraphs
* ```
*/
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node requestUse;
DataFlow::CallNode requestUse;
API::Node requestNode;
TyphoeusHttpRequest() {
@@ -20,6 +20,8 @@ class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
this = requestUse.asExpr().getExpr()
}
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {

View File

@@ -7,7 +7,7 @@
*/
private import AST
private import codeql.ruby.regexp.RegExpTreeView as RETV
private import codeql.ruby.security.performance.RegExpTreeView as RETV
/** Holds if `n` appears in the desugaring of some other node. */
predicate isDesugared(AstNode n) {

View File

@@ -0,0 +1,306 @@
/**
* Provides precicates for reasoning about bad tag filter vulnerabilities.
*/
import performance.ReDoSUtil
/**
* A module for determining if a regexp matches a given string,
* and reasoning about which capture groups are filled by a given string.
*/
private module RegexpMatching {
/**
* A class to test whether a regular expression matches a string.
* Override this class and extend `test`/`testWithGroups` to configure which strings should be tested for acceptance by this regular expression.
* The result can afterwards be read from the `matches` predicate.
*
* Strings in the `testWithGroups` predicate are also tested for which capture groups are filled by the given string.
* The result is available in the `fillCaptureGroup` predicate.
*/
abstract class MatchedRegExp extends RegExpTerm {
MatchedRegExp() { this.isRootTerm() }
/**
* Holds if it should be tested whether this regular expression matches `str`.
*
* If `ignorePrefix` is true, then a regexp without a start anchor will be treated as if it had a start anchor.
* E.g. a regular expression `/foo$/` will match any string that ends with "foo",
* but if `ignorePrefix` is true, it will only match "foo".
*/
predicate test(string str, boolean ignorePrefix) {
none() // maybe overriden 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
}
/**
* Holds if this RegExp matches `str`, where `str` is either in the `test` or `testWithGroups` predicate.
*/
final predicate matches(string str) {
exists(State state | state = getAState(this, str.length() - 1, str, _) |
epsilonSucc*(state) = Accept(_)
)
}
/**
* Holds if matching `str` may fill capture group number `g`.
* Only holds if `str` is in the `testWithGroups` predicate.
*/
final predicate fillsCaptureGroup(string str, int g) {
exists(State s |
s = getAStateThatReachesAccept(this, _, str, _) and
g = group(s.getRepr())
)
}
}
/**
* Gets a state the regular expression `reg` can be in after matching the `i`th char in `str`.
* The regular expression is modelled as a non-determistic finite automaton,
* the regular expression can therefore be in multiple states after matching a character.
*
* It's a forward search to all possible states, and there is thus no guarantee that the state is on a path to an accepting state.
*/
private State getAState(MatchedRegExp reg, int i, string str, boolean ignorePrefix) {
// start state, the -1 position before any chars have been matched
i = -1 and
(
reg.test(str, ignorePrefix)
or
reg.testWithGroups(str, ignorePrefix)
) and
result.getRepr().getRootTerm() = reg and
isStartState(result)
or
// recursive case
result = getAStateAfterMatching(reg, _, str, i, _, ignorePrefix)
}
/**
* Gets the next state after the `prev` state from `reg`.
* `prev` is the state after matching `fromIndex` chars in `str`,
* and the result is the state after matching `toIndex` chars in `str`.
*
* This predicate is used as a step relation in the forwards search (`getAState`),
* and also as a step relation in the later backwards search (`getAStateThatReachesAccept`).
*/
private State getAStateAfterMatching(
MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
) {
// the basic recursive case - outlined into a noopt helper to make performance work out.
result = getAStateAfterMatchingAux(reg, prev, str, toIndex, fromIndex, ignorePrefix)
or
// we can skip past word boundaries if the next char is a non-word char.
fromIndex = toIndex and
prev.getRepr() instanceof RegExpWordBoundary and
prev = getAState(reg, toIndex, str, ignorePrefix) and
after(prev.getRepr()) = result and
str.charAt(toIndex + 1).regexpMatch("\\W") // \W matches any non-word char.
}
pragma[noopt]
private State getAStateAfterMatchingAux(
MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
) {
prev = getAState(reg, fromIndex, str, ignorePrefix) and
fromIndex = toIndex - 1 and
exists(string char | char = str.charAt(toIndex) | specializedDeltaClosed(prev, char, result)) and
not discardedPrefixStep(prev, result, ignorePrefix)
}
/** Holds if a step from `prev` to `next` should be discarded when the `ignorePrefix` flag is set. */
private predicate discardedPrefixStep(State prev, State next, boolean ignorePrefix) {
prev = mkMatch(any(RegExpRoot r)) and
ignorePrefix = true and
next = prev
}
// The `deltaClosed` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
private predicate specializedDeltaClosed(State prev, string char, State next) {
deltaClosed(prev, specializedGetAnInputSymbolMatching(char), next)
}
// The `getAnInputSymbolMatching` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
pragma[noinline]
private InputSymbol specializedGetAnInputSymbolMatching(string char) {
exists(string s, MatchedRegExp r |
r.test(s, _)
or
r.testWithGroups(s, _)
|
char = s.charAt(_)
) and
result = getAnInputSymbolMatching(char)
}
/**
* Gets the `i`th state on a path to the accepting state when `reg` matches `str`.
* Starts with an accepting state as found by `getAState` and searches backwards
* to the start state through the reachable states (as found by `getAState`).
*
* This predicate holds the invariant that the result state can be reached with `i` steps from a start state,
* and an accepting state can be found after (`str.length() - 1 - i`) steps from the result.
* The result state is therefore always on a valid path where `reg` accepts `str`.
*
* This predicate is only used to find which capture groups a regular expression has filled,
* and thus the search is only performed for the strings in the `testWithGroups(..)` predicate.
*/
private State getAStateThatReachesAccept(
MatchedRegExp reg, int i, string str, boolean ignorePrefix
) {
// base case, reaches an accepting state from the last state in `getAState(..)`
reg.testWithGroups(str, ignorePrefix) and
i = str.length() - 1 and
result = getAState(reg, i, str, ignorePrefix) and
epsilonSucc*(result) = Accept(_)
or
// recursive case. `next` is the next state to be matched after matching `prev`.
// this predicate is doing a backwards search, so `prev` is the result we are looking for.
exists(State next, State prev, int fromIndex, int toIndex |
next = getAStateThatReachesAccept(reg, toIndex, str, ignorePrefix) and
next = getAStateAfterMatching(reg, prev, str, toIndex, fromIndex, ignorePrefix) and
i = fromIndex and
result = prev
)
}
/** Gets the capture group number that `term` belongs to. */
private int group(RegExpTerm term) {
exists(RegExpGroup grp | grp.getNumber() = result | term.getParent*() = grp)
}
}
/** A class to test whether a regular expression matches certain HTML tags. */
class HTMLMatchingRegExp extends RegexpMatching::MatchedRegExp {
HTMLMatchingRegExp() {
// the regexp must mention "<" and ">" explicitly.
forall(string angleBracket | angleBracket = ["<", ">"] |
any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
this
)
}
override predicate testWithGroups(string str, boolean ignorePrefix) {
ignorePrefix = true and
str = ["<!-- foo -->", "<!-- foo --!>", "<!- foo ->", "<foo>", "<script>"]
}
override predicate test(string str, boolean ignorePrefix) {
ignorePrefix = true and
str =
[
"<!-- foo -->", "<!- foo ->", "<!-- foo --!>", "<!-- foo\n -->", "<script>foo</script>",
"<script \n>foo</script>", "<script >foo\n</script>", "<foo ></foo>", "<foo>",
"<foo src=\"foo\"></foo>", "<script>", "<script src=\"foo\"></script>",
"<script src='foo'></script>", "<SCRIPT>foo</SCRIPT>", "<script\tsrc=\"foo\"/>",
"<script\tsrc='foo'></script>", "<sCrIpT>foo</ScRiPt>", "<script src=\"foo\">foo</script >",
"<script src=\"foo\">foo</script foo=\"bar\">", "<script src=\"foo\">foo</script\t\n bar>"
]
}
}
/**
* Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
*
* When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
*/
predicate isBadRegexpFilter(HTMLMatchingRegExp regexp, string msg) {
// CVE-2021-33829 - matching both "<!-- foo -->" and "<!-- foo --!>", but in different capture groups
regexp.matches("<!-- foo -->") and
regexp.matches("<!-- foo --!>") and
exists(int a, int b | a != b |
regexp.fillsCaptureGroup("<!-- foo -->", a) and
// <!-- foo --> might be ambigously parsed (matching both capture groups), and that is ok here.
regexp.fillsCaptureGroup("<!-- foo --!>", b) and
not regexp.fillsCaptureGroup("<!-- foo --!>", a) and
msg =
"Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
+ a + " and comments ending with --!> are matched with capture group " +
strictconcat(int i | regexp.fillsCaptureGroup("<!-- foo --!>", i) | i.toString(), ", ") +
"."
)
or
// CVE-2020-17480 - matching "<!-- foo -->" and other tags, but not "<!-- foo --!>".
exists(int group, int other |
group != other and
regexp.fillsCaptureGroup("<!-- foo -->", group) and
regexp.fillsCaptureGroup("<foo>", other) and
not regexp.matches("<!-- foo --!>") and
not regexp.fillsCaptureGroup("<!-- foo -->", any(int i | i != group)) and
not regexp.fillsCaptureGroup("<!- foo ->", group) and
not regexp.fillsCaptureGroup("<foo>", group) and
not regexp.fillsCaptureGroup("<script>", group) and
msg =
"This regular expression only parses --> (capture group " + group +
") and not --!> as a HTML comment end tag."
)
or
regexp.matches("<!-- foo -->") and
not regexp.matches("<!-- foo\n -->") and
not regexp.matches("<!- foo ->") and
not regexp.matches("<foo>") and
not regexp.matches("<script>") and
msg = "This regular expression does not match comments containing newlines."
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<foo ></foo>") and
(
not regexp.matches("<script \n>foo</script>") and
msg = "This regular expression matches <script></script>, but not <script \\n></script>"
or
not regexp.matches("<script >foo\n</script>") and
msg = "This regular expression matches <script>...</script>, but not <script >...\\n</script>"
)
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<script src='foo'></script>") and
not regexp.matches("<foo>") and
msg = "This regular expression does not match script tags where the attribute uses single-quotes."
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src='foo'></script>") and
not regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<foo>") and
msg = "This regular expression does not match script tags where the attribute uses double-quotes."
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src='foo'></script>") and
not regexp.matches("<script\tsrc='foo'></script>") and
not regexp.matches("<foo>") and
not regexp.matches("<foo src=\"foo\"></foo>") and
msg = "This regular expression does not match script tags where tabs are used between attributes."
or
regexp.matches("<script>foo</script>") and
not RegExpFlags::isIgnoreCase(regexp) and
not regexp.matches("<foo>") and
not regexp.matches("<foo ></foo>") and
(
not regexp.matches("<SCRIPT>foo</SCRIPT>") and
msg = "This regular expression does not match upper case <SCRIPT> tags."
or
not regexp.matches("<sCrIpT>foo</ScRiPt>") and
regexp.matches("<SCRIPT>foo</SCRIPT>") and
msg = "This regular expression does not match mixed case <sCrIpT> tags."
)
or
regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<foo>") and
not regexp.matches("<foo ></foo>") and
(
not regexp.matches("<script src=\"foo\">foo</script >") and
msg = "This regular expression does not match script end tags like </script >."
or
not regexp.matches("<script src=\"foo\">foo</script foo=\"bar\">") and
msg = "This regular expression does not match script end tags like </script foo=\"bar\">."
or
not regexp.matches("<script src=\"foo\">foo</script\t\n bar>") and
msg = "This regular expression does not match script end tags like </script\\t\\n bar>."
)
}

View File

@@ -0,0 +1,51 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* server side request forgery, as well as extension points for adding your own.
*/
private import ruby
private import codeql.ruby.ApiGraphs
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.Concepts
private import codeql.ruby.dataflow.Sanitizers
/**
* Provides default sources, sinks and sanitizers for reasoning about
* server side request forgery, as well as extension points for adding your own.
*/
module ServerSideRequestForgery {
/**
* A data flow source for server side request forgery vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for server side request forgery vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for server side request forgery vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A sanitizer guard for "URL redirection" vulnerabilities.
*/
abstract 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 {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
}
/** The URL of an HTTP request, considered as a sink. */
class HttpRequestAsSink extends Sink {
HttpRequestAsSink() { exists(HTTP::Client::Request req | req.getURL() = this) }
}
/** String interpolation with a fixed prefix, considered as a flow sanitizer. */
class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { }
}

View File

@@ -0,0 +1,33 @@
/**
* Provides a taint-tracking configuration for detecting
* "Server side request forgery" vulnerabilities.
*
* Note, for performance reasons: only import this file if `Configuration` is needed,
* otherwise `ServerSideRequestForgeryCustomizations` should be imported instead.
*/
import codeql.ruby.DataFlow::DataFlow::PathGraph
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery
import codeql.ruby.dataflow.BarrierGuards
/**
* A taint-tracking configuration for detecting
* "Server side request forgery" vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ServerSideRequestForgery" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
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 SanitizerGuard or
guard instanceof StringConstCompare or
guard instanceof StringConstArrayInclusionCall
}
}

View File

@@ -9,6 +9,7 @@ private import codeql.ruby.DataFlow
private import codeql.ruby.Concepts
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.dataflow.Sanitizers
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -57,9 +58,9 @@ module UrlRedirect {
this = e.getRedirectLocation() and
// As a rough heuristic, assume that methods with these names are handlers for POST/PUT/PATCH/DELETE requests,
// which are not as vulnerable to URL redirection because browsers will not initiate them from clicking a link.
not this.getEnclosingCallable()
.asCallable()
.(Method)
not this.asExpr()
.getExpr()
.getEnclosingMethod()
.getName()
.regexpMatch(".*(create|update|destroy).*")
)
@@ -85,6 +86,8 @@ module UrlRedirect {
}
/**
* A string interpolation, seen as a sanitizer for "URL redirection" vulnerabilities.
*
* String interpolation is considered safe, provided the string is prefixed by a non-tainted value.
* In most cases this will prevent the tainted value from controlling e.g. the host of the URL.
*
@@ -103,11 +106,7 @@ module UrlRedirect {
*
* We currently don't catch these cases.
*/
class StringInterpolationAsSanitizer extends Sanitizer {
StringInterpolationAsSanitizer() {
exists(StringlikeLiteral str, int n | str.getComponent(n) = this.asExpr().getExpr() and n > 0)
}
}
class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { }
/**
* These methods return a new `ActionController::Parameters` or a `Hash` containing a subset of

View File

@@ -8,9 +8,9 @@ private import codeql.ruby.AST as AST
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.regexp.ParseRegExp as RegExp
private import codeql.ruby.regexp.RegExpTreeView
private import codeql.ruby.regexp.SuperlinearBackTracking
private import codeql.ruby.security.performance.ParseRegExp as RegExp
private import codeql.ruby.security.performance.RegExpTreeView
private import codeql.ruby.security.performance.SuperlinearBackTracking
module PolynomialReDoS {
/**

View File

@@ -140,12 +140,10 @@ class RegExpRoot extends RegExpTerm {
predicate isRelevant() {
// there is at least one repetition
getRoot(any(InfiniteRepetitionQuantifier q)) = this and
// there are no lookbehinds
not exists(RegExpLookbehind lbh | getRoot(lbh) = this) and
// is actually used as a RegExp
this.isUsedAsRegExp() //and
// // pragmatic performance optimization: ignore minified files.
// not getRootTerm().getParent().(Expr).getTopLevel().isMinified()
isUsedAsRegExp() and
// not excluded for library specific reasons
not isExcluded(getRootTerm().getParent())
}
}
@@ -156,38 +154,68 @@ private class RegexpCharacterConstant extends RegExpConstant {
RegexpCharacterConstant() { this.isCharacter() }
}
/**
* A regexp term that is relevant for this ReDoS analysis.
*/
class RelevantRegExpTerm extends RegExpTerm {
RelevantRegExpTerm() { getRoot(this).isRelevant() }
}
/**
* Holds if `term` is the chosen canonical representative for all terms with string representation `str`.
* The string representation includes which flags are used with the regular expression.
*
* Using canonical representatives gives a huge performance boost when working with tuples containing multiple `InputSymbol`s.
* The number of `InputSymbol`s is decreased by 3 orders of magnitude or more in some larger benchmarks.
*/
private predicate isCanonicalTerm(RegExpTerm term, string str) {
private predicate isCanonicalTerm(RelevantRegExpTerm term, string str) {
term =
rank[1](RegExpTerm t, Location loc, File file |
min(RelevantRegExpTerm t, Location loc, File file |
loc = t.getLocation() and
file = t.getFile() and
str = t.getRawValue()
str = t.getRawValue() + "|" + getCanonicalizationFlags(t.getRootTerm())
|
t order by t.getFile().getRelativePath(), loc.getStartLine(), loc.getStartColumn()
)
}
/**
* Gets a string reperesentation of the flags used with the regular expression.
* Only the flags that are relevant for the canonicalization are included.
*/
string getCanonicalizationFlags(RegExpTerm root) {
root.isRootTerm() and
(if RegExpFlags::isIgnoreCase(root) then result = "i" else result = "")
}
/**
* An abstract input symbol, representing a set of concrete characters.
*/
private newtype TInputSymbol =
/** An input symbol corresponding to character `c`. */
Char(string c) {
c = any(RegexpCharacterConstant cc | getRoot(cc).isRelevant()).getValue().charAt(_)
c =
any(RegexpCharacterConstant cc |
cc instanceof RelevantRegExpTerm and
not RegExpFlags::isIgnoreCase(cc.getRootTerm())
).getValue().charAt(_)
or
// normalize everything to lower case if the regexp is case insensitive
c =
any(RegexpCharacterConstant cc, string char |
cc instanceof RelevantRegExpTerm and
RegExpFlags::isIgnoreCase(cc.getRootTerm()) and
char = cc.getValue().charAt(_)
|
char.toLowerCase()
)
} or
/**
* An input symbol representing all characters matched by
* a (non-universal) character class that has string representation `charClassString`.
*/
CharClass(string charClassString) {
exists(RegExpTerm term | term.getRawValue() = charClassString | getRoot(term).isRelevant()) and
exists(RegExpTerm recc | isCanonicalTerm(recc, charClassString) |
exists(RelevantRegExpTerm recc | isCanonicalTerm(recc, charClassString) |
recc instanceof RegExpCharacterClass and
not recc.(RegExpCharacterClass).isUniversalClass()
or
@@ -254,7 +282,7 @@ class InputSymbol extends TInputSymbol {
/**
* An abstract input symbol that represents a character class.
*/
abstract private class CharacterClass extends InputSymbol {
abstract class CharacterClass extends InputSymbol {
/**
* Gets a character that is relevant for intersection-tests involving this
* character class.
@@ -277,7 +305,7 @@ abstract private class CharacterClass extends InputSymbol {
/**
* Gets a character matched by this character class.
*/
string choose() { result = this.getARelevantChar() and this.matches(result) }
string choose() { result = getARelevantChar() and matches(result) }
}
/**
@@ -289,6 +317,19 @@ private module CharacterClasses {
*/
pragma[noinline]
predicate hasChildThatMatches(RegExpCharacterClass cc, string char) {
if RegExpFlags::isIgnoreCase(cc.getRootTerm())
then
// normalize everything to lower case if the regexp is case insensitive
exists(string c | hasChildThatMatchesIgnoringCasingFlags(cc, c) | char = c.toLowerCase())
else hasChildThatMatchesIgnoringCasingFlags(cc, char)
}
/**
* Holds if the character class `cc` has a child (constant or range) that matches `char`.
* Ignores whether the character class is inside a regular expression that has the ignore case flag.
*/
pragma[noinline]
predicate hasChildThatMatchesIgnoringCasingFlags(RegExpCharacterClass cc, string char) {
exists(getCanonicalCharClass(cc)) and
exists(RegExpTerm child | child = cc.getAChild() |
char = child.(RegexpCharacterConstant).getValue()
@@ -433,7 +474,7 @@ private module CharacterClasses {
char = "0123456789".charAt(_)
or
clazz = "s" and
char = [" ", "\t", "\r", "\n", 11.toUnicode(), 12.toUnicode()] // 11.toUnicode() = \v, 12.toUnicode() = \f'
char = [" ", "\t", "\r", "\n", 11.toUnicode(), 12.toUnicode()] // 11.toUnicode() = \v, 12.toUnicode() = \f
or
clazz = "w" and
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(_)
@@ -477,7 +518,7 @@ private module CharacterClasses {
result = ["0", "9"]
or
cc.getValue() = "s" and
result = [" "]
result = " "
or
cc.getValue() = "w" and
result = ["a", "Z", "_", "0", "9"]
@@ -490,7 +531,7 @@ private module CharacterClasses {
result = "9"
or
cc.getValue() = "s" and
result = [" "]
result = " "
or
cc.getValue() = "w" and
result = "a"
@@ -604,7 +645,7 @@ private State before(RegExpTerm t) { result = Match(t, 0) }
/**
* Gets a state the NFA may be in after matching `t`.
*/
private State after(RegExpTerm t) {
State after(RegExpTerm t) {
exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt))
or
exists(RegExpSequence seq, int i | t = seq.getChild(i) |
@@ -633,7 +674,14 @@ private State after(RegExpTerm t) {
predicate delta(State q1, EdgeLabel lbl, State q2) {
exists(RegexpCharacterConstant s, int i |
q1 = Match(s, i) and
lbl = Char(s.getValue().charAt(i)) and
(
not RegExpFlags::isIgnoreCase(s.getRootTerm()) and
lbl = Char(s.getValue().charAt(i))
or
// normalize everything to lower case if the regexp is case insensitive
RegExpFlags::isIgnoreCase(s.getRootTerm()) and
exists(string c | c = s.getValue().charAt(i) | lbl = Char(c.toLowerCase()))
) and
(
q2 = Match(s, i + 1)
or
@@ -643,20 +691,20 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
)
or
exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) |
if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot()
if RegExpFlags::isDotAll(dot.getRootTerm()) then lbl = Any() else lbl = Dot()
)
or
exists(RegExpCharacterClass cc |
cc.isUniversalClass() and q1 = before(cc) and lbl = Any() and q2 = after(cc)
or
q1 = before(cc) and
lbl = CharClass(cc.getRawValue()) and
lbl = CharClass(cc.getRawValue() + "|" + getCanonicalizationFlags(cc.getRootTerm())) and
q2 = after(cc)
)
or
exists(RegExpCharacterClassEscape cc |
q1 = before(cc) and
lbl = CharClass(cc.getRawValue()) and
lbl = CharClass(cc.getRawValue() + "|" + getCanonicalizationFlags(cc.getRootTerm())) and
q2 = after(cc)
)
or
@@ -729,16 +777,27 @@ RegExpRoot getRoot(RegExpTerm term) {
result = getRoot(term.getParent())
}
private newtype TState =
Match(RegExpTerm t, int i) {
getRoot(t).isRelevant() and
(
i = 0
or
exists(t.(RegexpCharacterConstant).getValue().charAt(i))
)
/**
* A state in the NFA.
*/
newtype TState =
/**
* A state representing that the NFA is about to match a term.
* `i` is used to index into multi-char literals.
*/
Match(RelevantRegExpTerm t, int i) {
i = 0
or
exists(t.(RegexpCharacterConstant).getValue().charAt(i))
} or
/**
* An accept state, where exactly the given input string is accepted.
*/
Accept(RegExpRoot l) { l.isRelevant() } or
/**
* An accept state, where the given input string, or any string that has this
* string as a prefix, is accepted.
*/
AcceptAnySuffix(RegExpRoot l) { l.isRelevant() }
/**
@@ -851,29 +910,26 @@ InputSymbol getAnInputSymbolMatching(string char) {
result = Any()
}
/**
* Holds if `state` is a start state.
*/
predicate isStartState(State state) {
state = mkMatch(any(RegExpRoot r))
or
exists(RegExpCaret car | state = after(car))
}
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
private module PrefixConstruction {
/**
* Holds if `state` starts the string matched by the regular expression.
*/
private predicate isStartState(State state) {
state instanceof StateInPumpableRegexp and
(
state = Match(any(RegExpRoot r), _)
or
exists(RegExpCaret car | state = after(car))
)
}
/**
* Holds if `state` is the textually last start state for the regular expression.
*/
private predicate lastStartState(State state) {
exists(RegExpRoot root |
state =
max(State s, Location l |
max(StateInPumpableRegexp s, Location l |
isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation()
|
s
@@ -1173,7 +1229,6 @@ private predicate isReDoSAttackable(RegExpTerm term, string pump, State s) {
* `prefixMsg` contains a friendly message for a prefix that reaches `s` (or `prefixMsg` is the empty string if the prefix is empty or if no prefix could be found).
*/
predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
not t.getRegExp().hasFreeSpacingFlag() and // exclude free-spacing mode regexes
isReDoSAttackable(t, pump, s) and
(
prefixMsg = "starting with '" + escape(PrefixConstruction::prefix(s)) + "' and " and

View File

@@ -2,6 +2,42 @@ private import codeql.ruby.ast.Literal as AST
private import codeql.Locations
private import ParseRegExp
/**
* Holds if the regular expression should not be considered.
*/
predicate isExcluded(RegExpParent parent) {
parent.(RegExpTerm).getRegExp().hasFreeSpacingFlag() // exclude free-spacing mode regexes
}
/**
* A module containing predicates for determining which flags a regular expression have.
*/
module RegExpFlags {
/**
* Holds if `root` has the `i` flag for case-insensitive matching.
*/
predicate isIgnoreCase(RegExpTerm root) {
root.isRootTerm() and
root.getLiteral().isIgnoreCase()
}
/**
* Gets the flags for `root`, or the empty string if `root` has no flags.
*/
string getFlags(RegExpTerm root) {
root.isRootTerm() and
result = root.getLiteral().getFlags()
}
/**
* Holds if `root` has the `s` flag for multi-line matching.
*/
predicate isDotAll(RegExpTerm root) {
root.isRootTerm() and
root.getLiteral().isDotAll()
}
}
/**
* An element containing a regular expression term, that is, either
* a string literal (parsed as a regular expression)
@@ -38,6 +74,10 @@ class RegExpLiteral extends TRegExpLiteral, RegExpParent {
predicate isDotAll() { re.hasMultilineFlag() }
predicate isIgnoreCase() { re.hasCaseInsensitiveFlag() }
string getFlags() { result = re.getFlagString() }
override string getAPrimaryQlClass() { result = "RegExpLiteral" }
}
@@ -398,6 +438,13 @@ private int toHex(string hex) {
result = 15 and hex = ["f", "F"]
}
/**
* A word boundary, that is, a regular expression term of the form `\b`.
*/
class RegExpWordBoundary extends RegExpEscape {
RegExpWordBoundary() { this.getUnescaped() = "b" }
}
/**
* A character class escape in a regular expression.
* That is, an escaped character that denotes multiple characters.

View File

@@ -99,11 +99,11 @@ predicate returnStep(Node nodeFrom, Node nodeTo) {
* to `z` inside `bar`, even though this content write happens _after_ `bar` is
* called.
*/
predicate basicStoreStep(Node nodeFrom, DataFlowPublic::LocalSourceNode nodeTo, string content) {
predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
// TODO: support SetterMethodCall inside TuplePattern
exists(ExprNodes::MethodCallCfgNode call |
content = getSetterCallAttributeName(call.getExpr()) and
nodeTo.(DataFlowPublic::ExprNode).getExprNode() = call.getReceiver() and
nodeTo.(DataFlowPrivate::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and
call.getExpr() instanceof AST::SetterMethodCall and
call.getArgument(call.getNumberOfArguments() - 1) =
nodeFrom.(DataFlowPublic::ExprNode).getExprNode()