mirror of
https://github.com/github/codeql.git
synced 2025-12-22 03:36:30 +01:00
Merge branch 'main' into rc/3.7
This commit is contained in:
BIN
ruby/Cargo.lock
generated
BIN
ruby/Cargo.lock
generated
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
class AstNode extends @ruby_ast_node_parent {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Location extends @location {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Wrapper = @ruby_body_statement or @ruby_block_body;
|
||||
|
||||
predicate astNodeInfo(AstNode child, AstNode parent, int primaryIndex, int secondaryIndex) {
|
||||
not parent instanceof Wrapper and
|
||||
exists(AstNode node, Location l |
|
||||
ruby_ast_node_info(node, parent, primaryIndex, _) and
|
||||
(
|
||||
not node instanceof Wrapper and secondaryIndex = 0 and child = node
|
||||
or
|
||||
node instanceof Wrapper and ruby_ast_node_info(child, node, secondaryIndex, _)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from AstNode node, AstNode parent, int parent_index, Location loc
|
||||
where
|
||||
node =
|
||||
rank[parent_index + 1](AstNode n, int i, int j | astNodeInfo(n, parent, i, j) | n order by i, j) and
|
||||
ruby_ast_node_info(node, _, _, loc)
|
||||
select node, parent, parent_index, loc
|
||||
@@ -0,0 +1,7 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_block, AstNode body, int index, AstNode child
|
||||
where ruby_block_body(ruby_block, body) and ruby_block_body_child(body, index, child)
|
||||
select ruby_block, index, child
|
||||
@@ -0,0 +1,7 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_class, AstNode body, int index, AstNode child
|
||||
where ruby_class_body(ruby_class, body) and ruby_body_statement_child(body, index, child)
|
||||
select ruby_class, index, child
|
||||
@@ -0,0 +1,8 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_do_block, AstNode body, int index, AstNode child
|
||||
where ruby_do_block_body(ruby_do_block, body) and ruby_body_statement_child(body, index, child)
|
||||
select ruby_do_block, index, child
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_method, int index, AstNode child
|
||||
where
|
||||
exists(AstNode body |
|
||||
ruby_method_body(ruby_method, body) and ruby_body_statement_child(body, index, child)
|
||||
)
|
||||
or
|
||||
ruby_method_body(ruby_method, child) and
|
||||
not child instanceof @ruby_body_statement and
|
||||
index = 0
|
||||
select ruby_method, index, child
|
||||
@@ -0,0 +1,8 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_module, AstNode body, int index, AstNode child
|
||||
where ruby_module_body(ruby_module, body) and ruby_body_statement_child(body, index, child)
|
||||
select ruby_module, index, child
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_singleton_class, AstNode body, int index, AstNode child
|
||||
where ruby_singleton_class_body(ruby_singleton_class, body) and ruby_body_statement_child(body, index, child)
|
||||
select ruby_singleton_class, index, child
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_singleton_method, int index, AstNode child
|
||||
where
|
||||
exists(AstNode body |
|
||||
ruby_singleton_method_body(ruby_singleton_method, body) and
|
||||
ruby_body_statement_child(body, index, child)
|
||||
)
|
||||
or
|
||||
ruby_singleton_method_body(ruby_singleton_method, child) and
|
||||
not child instanceof @ruby_body_statement and
|
||||
index = 0
|
||||
select ruby_singleton_method, index, child
|
||||
@@ -0,0 +1,30 @@
|
||||
description: Wrap class, module, method, and block bodies in a named node
|
||||
compatibility: full
|
||||
|
||||
ruby_block_body.rel: delete
|
||||
ruby_block_body_def.rel: delete
|
||||
ruby_block_body_child.rel: delete
|
||||
ruby_block_child.rel: run ruby_block_child.qlo
|
||||
|
||||
ruby_body_statement_child.rel: delete
|
||||
ruby_body_statement_def.rel: delete
|
||||
|
||||
ruby_class_body.rel: delete
|
||||
ruby_class_child.rel: run ruby_class_child.qlo
|
||||
|
||||
ruby_do_block_body.rel: delete
|
||||
ruby_do_block_child.rel: run ruby_do_block_child.qlo
|
||||
|
||||
ruby_method_body.rel: delete
|
||||
ruby_method_child.rel: run ruby_method_child.qlo
|
||||
|
||||
ruby_module_body.rel: delete
|
||||
ruby_module_child.rel: run ruby_module_child.qlo
|
||||
|
||||
ruby_singleton_class_body.rel: delete
|
||||
ruby_singleton_class_child.rel: run ruby_singleton_class_child.qlo
|
||||
|
||||
ruby_singleton_method_body.rel: delete
|
||||
ruby_singleton_method_child.rel: run ruby_singleton_method_child.qlo
|
||||
|
||||
ruby_ast_node_info.rel: run ruby_ast_node_info.qlo
|
||||
@@ -11,7 +11,7 @@ flate2 = "1.0"
|
||||
node-types = { path = "../node-types" }
|
||||
tree-sitter = "0.19"
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "ad1043283b1f9daf4aad381b6a81f18a5a27fe7e" }
|
||||
clap = "3.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
|
||||
@@ -12,4 +12,4 @@ node-types = { path = "../node-types" }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "ad1043283b1f9daf4aad381b6a81f18a5a27fe7e" }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.DataFlow::DataFlow
|
||||
import codeql.ruby.dataflow.internal.DataFlowPrivate
|
||||
import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
|
||||
@@ -11,5 +12,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
or
|
||||
not isNonConstantExpr(n.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import codeql.ruby.dataflow.SSA
|
||||
import codeql.ruby.dataflow.internal.SsaImplCommon::Consistency
|
||||
import codeql.ruby.dataflow.internal.SsaImpl::Consistency as Consistency
|
||||
|
||||
class MyRelevantDefinition extends RelevantDefinition, Ssa::Definition {
|
||||
class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definition {
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
query predicate nonUniqueDef = Consistency::nonUniqueDef/4;
|
||||
|
||||
query predicate readWithoutDef = Consistency::readWithoutDef/3;
|
||||
|
||||
query predicate deadDef = Consistency::deadDef/2;
|
||||
|
||||
query predicate notDominatedByDef = Consistency::notDominatedByDef/4;
|
||||
|
||||
4
ruby/ql/lib/change-notes/2022-08-16-active-resource.md
Normal file
4
ruby/ql/lib/change-notes/2022-08-16-active-resource.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Accesses of ActiveResource models are now recognized as HTTP requests.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* Changed the `HTTP::Client::Request` concept from using `MethodCall` as base class, to using `DataFlow::Node` as base class. Any class that extends `HTTP::Client::Request::Range` must be changed, but if you only use the member predicates of `HTTP::Client::Request`, no changes are required.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: fix
|
||||
---
|
||||
* Fixed an issue in the taint tracking analysis where implicit reads were not allowed by default in sinks or additional taint steps that used flow states.
|
||||
5
ruby/ql/lib/change-notes/2022-09-12-uppercase.md
Normal file
5
ruby/ql/lib/change-notes/2022-09-12-uppercase.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* Some classes/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
|
||||
The old name still exists as a deprecated alias.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Uses of `ActionView::FileSystemResolver` are now recognized as filesystem accesses.
|
||||
@@ -60,10 +60,10 @@ class AstNode extends TAstNode {
|
||||
final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
|
||||
|
||||
/** Gets the enclosing module, if any. */
|
||||
ModuleBase getEnclosingModule() { result = getEnclosingModule(scopeOfInclSynth(this)) }
|
||||
final ModuleBase getEnclosingModule() { result = getEnclosingModule(scopeOfInclSynth(this)) }
|
||||
|
||||
/** Gets the enclosing method, if any. */
|
||||
MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) }
|
||||
final MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
cached
|
||||
|
||||
@@ -33,7 +33,7 @@ module API {
|
||||
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
||||
*
|
||||
* For example, a simplified way to get arguments to `Foo.bar` would be
|
||||
* ```codeql
|
||||
* ```ql
|
||||
* API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink()
|
||||
* ```
|
||||
*
|
||||
@@ -97,6 +97,7 @@ module API {
|
||||
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
||||
* See `asSource()` for examples.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAValueReachableFromSource() {
|
||||
exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
@@ -382,11 +383,17 @@ module API {
|
||||
bindingset[this]
|
||||
EntryPoint() { any() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getASource`. */
|
||||
deprecated DataFlow::LocalSourceNode getAUse() { none() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getASink`. */
|
||||
deprecated DataFlow::Node getARhs() { none() }
|
||||
|
||||
/** Gets a data-flow node corresponding to a use-node for this entry point. */
|
||||
DataFlow::LocalSourceNode getAUse() { none() }
|
||||
DataFlow::LocalSourceNode getASource() { none() }
|
||||
|
||||
/** Gets a data-flow node corresponding to a def-node for this entry point. */
|
||||
DataFlow::Node getARhs() { none() }
|
||||
DataFlow::Node getASink() { none() }
|
||||
|
||||
/** Gets a call corresponding to a method access node for this entry point. */
|
||||
DataFlow::CallNode getACall() { none() }
|
||||
@@ -504,7 +511,7 @@ module API {
|
||||
or
|
||||
parameterStep(_, defCand(), nd)
|
||||
or
|
||||
nd = any(EntryPoint entry).getAUse()
|
||||
nd = any(EntryPoint entry).getASource()
|
||||
or
|
||||
nd = any(EntryPoint entry).getACall()
|
||||
}
|
||||
@@ -553,7 +560,7 @@ module API {
|
||||
// If a call node is relevant as a use-node, treat its arguments as def-nodes
|
||||
argumentStep(_, useCandFwd(), rhs)
|
||||
or
|
||||
rhs = any(EntryPoint entry).getARhs()
|
||||
rhs = any(EntryPoint entry).getASink()
|
||||
}
|
||||
|
||||
/** Gets a data flow node that flows to the RHS of a def-node. */
|
||||
@@ -712,9 +719,9 @@ module API {
|
||||
pred = root() and
|
||||
lbl = Label::entryPoint(entry)
|
||||
|
|
||||
succ = MkDef(entry.getARhs())
|
||||
succ = MkDef(entry.getASink())
|
||||
or
|
||||
succ = MkUse(entry.getAUse())
|
||||
succ = MkUse(entry.getASource())
|
||||
or
|
||||
succ = MkMethodAccessNode(entry.getACall())
|
||||
)
|
||||
@@ -832,7 +839,7 @@ module API {
|
||||
|
||||
LabelEntryPoint() { this = MkLabelEntryPoint(name) }
|
||||
|
||||
override string toString() { result = name }
|
||||
override string toString() { result = "entryPoint(\"" + name + "\")" }
|
||||
|
||||
/** Gets the name of the entry point. */
|
||||
API::EntryPoint getName() { result = name }
|
||||
|
||||
@@ -222,7 +222,7 @@ class HtmlEscaping extends Escaping {
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP-related APIs. */
|
||||
module HTTP {
|
||||
module Http {
|
||||
/** Provides classes for modeling HTTP servers. */
|
||||
module Server {
|
||||
/**
|
||||
@@ -465,7 +465,7 @@ module HTTP {
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `HttpResponse` instead.
|
||||
*/
|
||||
abstract class Range extends HTTP::Server::HttpResponse::Range {
|
||||
abstract class Range extends Http::Server::HttpResponse::Range {
|
||||
/** Gets the data-flow node that specifies the location of this HTTP redirect response. */
|
||||
abstract DataFlow::Node getRedirectLocation();
|
||||
}
|
||||
@@ -474,13 +474,15 @@ module HTTP {
|
||||
|
||||
/** Provides classes for modeling HTTP clients. */
|
||||
module Client {
|
||||
import codeql.ruby.internal.ConceptsShared::Http::Client as SC
|
||||
|
||||
/**
|
||||
* A method call that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Request::Range` instead.
|
||||
*/
|
||||
class Request extends MethodCall instanceof Request::Range {
|
||||
class Request extends SC::Request instanceof Request::Range {
|
||||
/** Gets a node which returns the body of the response */
|
||||
DataFlow::Node getResponseBody() { result = super.getResponseBody() }
|
||||
|
||||
@@ -490,24 +492,19 @@ module HTTP {
|
||||
* 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.
|
||||
*/
|
||||
deprecated DataFlow::Node getURL() { result = super.getURL() or result = super.getAUrlPart() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow 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 getAUrlPart() { result = super.getAUrlPart() }
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
string getFramework() { result = super.getFramework() }
|
||||
deprecated DataFlow::Node getURL() {
|
||||
result = super.getURL() or result = Request::Range.super.getAUrlPart()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled.
|
||||
*/
|
||||
predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
super.disablesCertificateValidation(disablingNode)
|
||||
deprecated predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
Request::Range.super.disablesCertificateValidation(disablingNode, _)
|
||||
or
|
||||
Request::Range.super.disablesCertificateValidation(disablingNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +516,7 @@ module HTTP {
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `Request` instead.
|
||||
*/
|
||||
abstract class Range extends MethodCall {
|
||||
abstract class Range extends SC::Request::Range {
|
||||
/** Gets a node which returns the body of the response */
|
||||
abstract DataFlow::Node getResponseBody();
|
||||
|
||||
@@ -532,20 +529,13 @@ module HTTP {
|
||||
deprecated DataFlow::Node getURL() { none() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow 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 getAUrlPart();
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
abstract string getFramework();
|
||||
|
||||
/**
|
||||
* DEPRECATED: override `disablesCertificateValidation/2` instead.
|
||||
*
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled.
|
||||
*/
|
||||
abstract predicate disablesCertificateValidation(DataFlow::Node disablingNode);
|
||||
deprecated predicate disablesCertificateValidation(DataFlow::Node disablingNode) { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,6 +550,9 @@ module HTTP {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for Http */
|
||||
deprecated module HTTP = Http;
|
||||
|
||||
/**
|
||||
* A data flow node that executes an operating system command,
|
||||
* for instance by spawning a new process.
|
||||
@@ -621,16 +614,12 @@ module CodeExecution {
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `XmlParserCall::Range` instead.
|
||||
*/
|
||||
class XmlParserCall extends DataFlow::Node {
|
||||
XmlParserCall::Range range;
|
||||
|
||||
XmlParserCall() { this = range }
|
||||
|
||||
class XmlParserCall extends DataFlow::Node instanceof XmlParserCall::Range {
|
||||
/** Gets the argument that specifies the XML content to be parsed. */
|
||||
DataFlow::Node getInput() { result = range.getInput() }
|
||||
DataFlow::Node getInput() { result = super.getInput() }
|
||||
|
||||
/** Holds if this XML parser call is configured to process external entities */
|
||||
predicate externalEntitiesEnabled() { range.externalEntitiesEnabled() }
|
||||
predicate externalEntitiesEnabled() { super.externalEntitiesEnabled() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new XML parsing APIs. */
|
||||
@@ -800,13 +789,9 @@ module CookieSecurityConfigurationSetting {
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Logging::Range` instead.
|
||||
*/
|
||||
class Logging extends DataFlow::Node {
|
||||
Logging::Range range;
|
||||
|
||||
Logging() { this = range }
|
||||
|
||||
class Logging extends DataFlow::Node instanceof Logging::Range {
|
||||
/** Gets an input that is logged. */
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new logging mechanisms. */
|
||||
|
||||
@@ -6,6 +6,7 @@ private import codeql.ruby.frameworks.Core
|
||||
private import codeql.ruby.frameworks.ActionCable
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveResource
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.ActiveSupport
|
||||
|
||||
@@ -51,25 +51,35 @@ class Ensure extends StmtSequence, TEnsure {
|
||||
|
||||
// Not defined by dispatch, as it should not be exposed
|
||||
Ruby::AstNode getBodyStmtChild(TBodyStmt b, int i) {
|
||||
result = any(Ruby::Method g | b = TMethod(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::SingletonMethod g | b = TSingletonMethod(g)).getChild(i)
|
||||
or
|
||||
exists(Ruby::Lambda g | b = TLambda(g) |
|
||||
result = g.getBody().(Ruby::DoBlock).getChild(i) or
|
||||
result = g.getBody().(Ruby::Block).getChild(i)
|
||||
exists(Ruby::Method g, Ruby::AstNode body | b = TMethod(g) and body = g.getBody() |
|
||||
result = body.(Ruby::BodyStatement).getChild(i)
|
||||
or
|
||||
i = 0 and result = body and not body instanceof Ruby::BodyStatement
|
||||
)
|
||||
or
|
||||
result = any(Ruby::DoBlock g | b = TDoBlock(g)).getChild(i)
|
||||
exists(Ruby::SingletonMethod g, Ruby::AstNode body |
|
||||
b = TSingletonMethod(g) and body = g.getBody()
|
||||
|
|
||||
result = body.(Ruby::BodyStatement).getChild(i)
|
||||
or
|
||||
i = 0 and result = body and not body instanceof Ruby::BodyStatement
|
||||
)
|
||||
or
|
||||
exists(Ruby::Lambda g | b = TLambda(g) |
|
||||
result = g.getBody().(Ruby::DoBlock).getBody().getChild(i) or
|
||||
result = g.getBody().(Ruby::Block).getBody().getChild(i)
|
||||
)
|
||||
or
|
||||
result = any(Ruby::DoBlock g | b = TDoBlock(g)).getBody().getChild(i)
|
||||
or
|
||||
result = any(Ruby::Program g | b = TToplevel(g)).getChild(i) and
|
||||
not result instanceof Ruby::BeginBlock
|
||||
or
|
||||
result = any(Ruby::Class g | b = TClassDeclaration(g)).getChild(i)
|
||||
result = any(Ruby::Class g | b = TClassDeclaration(g)).getBody().getChild(i)
|
||||
or
|
||||
result = any(Ruby::SingletonClass g | b = TSingletonClass(g)).getChild(i)
|
||||
result = any(Ruby::SingletonClass g | b = TSingletonClass(g)).getBody().getChild(i)
|
||||
or
|
||||
result = any(Ruby::Module g | b = TModuleDeclaration(g)).getChild(i)
|
||||
result = any(Ruby::Module g | b = TModuleDeclaration(g)).getBody().getChild(i)
|
||||
or
|
||||
result = any(Ruby::Begin g | b = TBeginExpr(g)).getChild(i)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class BraceBlockReal extends BraceBlock, TBraceBlockReal {
|
||||
toGenerated(result) = g.getParameters().getChild(n)
|
||||
}
|
||||
|
||||
final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
|
||||
final override Stmt getStmt(int i) { toGenerated(result) = g.getBody().getChild(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -137,14 +137,7 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
Method lookupMethod(Module m, string name) {
|
||||
// The syntax_suggest library redefines Kernel.require/require_relative.
|
||||
// Somehow this causes performance issues on ruby/ruby. As a workaround
|
||||
// we exclude 'require' and 'require_relative'.
|
||||
// TODO: find the actual cause of the slowdown and fix things properly.
|
||||
not name = ["require", "require_relative"] and
|
||||
TMethod(result) = lookupMethodOrConst(m, name)
|
||||
}
|
||||
Method lookupMethod(Module m, string name) { TMethod(result) = lookupMethodOrConst(m, name) }
|
||||
|
||||
cached
|
||||
Expr lookupConst(Module m, string name) {
|
||||
|
||||
@@ -128,7 +128,7 @@ private AstNode specialParentOfInclSynth(AstNode n) {
|
||||
n =
|
||||
[
|
||||
result.(Namespace).getScopeExpr(), result.(ClassDeclaration).getSuperclassExpr(),
|
||||
result.(SingletonMethod).getObject()
|
||||
result.(SingletonMethod).getObject(), result.(SingletonClass).getValue()
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -311,15 +311,15 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "Block" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final BlockBody getBody() { ruby_block_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `parameters`. */
|
||||
final BlockParameters getParameters() { ruby_block_parameters(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_block_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_block_parameters(this, result) or ruby_block_child(this, _, result)
|
||||
ruby_block_body(this, result) or ruby_block_parameters(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,18 @@ module Ruby {
|
||||
final override AstNode getAFieldOrChild() { ruby_block_argument_child(this, result) }
|
||||
}
|
||||
|
||||
/** A class representing `block_body` nodes. */
|
||||
class BlockBody extends @ruby_block_body, AstNode {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "BlockBody" }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_block_body_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() { ruby_block_body_child(this, _, result) }
|
||||
}
|
||||
|
||||
/** A class representing `block_parameter` nodes. */
|
||||
class BlockParameter extends @ruby_block_parameter, AstNode {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
@@ -364,6 +376,18 @@ module Ruby {
|
||||
}
|
||||
}
|
||||
|
||||
/** A class representing `body_statement` nodes. */
|
||||
class BodyStatement extends @ruby_body_statement, AstNode {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "BodyStatement" }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_body_statement_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() { ruby_body_statement_child(this, _, result) }
|
||||
}
|
||||
|
||||
/** A class representing `break` nodes. */
|
||||
class Break extends @ruby_break, AstNode {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
@@ -468,20 +492,20 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "Class" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final BodyStatement getBody() { ruby_class_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
final AstNode getName() { ruby_class_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `superclass`. */
|
||||
final Superclass getSuperclass() { ruby_class_superclass(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_class_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_class_body(this, result) or
|
||||
ruby_class_def(this, result) or
|
||||
ruby_class_superclass(this, result) or
|
||||
ruby_class_child(this, _, result)
|
||||
ruby_class_superclass(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,15 +616,15 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "DoBlock" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final BodyStatement getBody() { ruby_do_block_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `parameters`. */
|
||||
final BlockParameters getParameters() { ruby_do_block_parameters(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_do_block_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_do_block_parameters(this, result) or ruby_do_block_child(this, _, result)
|
||||
ruby_do_block_body(this, result) or ruby_do_block_parameters(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1106,20 +1130,20 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "Method" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final AstNode getBody() { ruby_method_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
final UnderscoreMethodName getName() { ruby_method_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `parameters`. */
|
||||
final MethodParameters getParameters() { ruby_method_parameters(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_method_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_method_body(this, result) or
|
||||
ruby_method_def(this, result) or
|
||||
ruby_method_parameters(this, result) or
|
||||
ruby_method_child(this, _, result)
|
||||
ruby_method_parameters(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1140,15 +1164,15 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "Module" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final BodyStatement getBody() { ruby_module_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
final AstNode getName() { ruby_module_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_module_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_module_def(this, result) or ruby_module_child(this, _, result)
|
||||
ruby_module_body(this, result) or ruby_module_def(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1504,15 +1528,15 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "SingletonClass" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final BodyStatement getBody() { ruby_singleton_class_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `value`. */
|
||||
final UnderscoreArg getValue() { ruby_singleton_class_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_singleton_class_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_singleton_class_def(this, result) or ruby_singleton_class_child(this, _, result)
|
||||
ruby_singleton_class_body(this, result) or ruby_singleton_class_def(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1521,6 +1545,9 @@ module Ruby {
|
||||
/** Gets the name of the primary QL class for this element. */
|
||||
final override string getAPrimaryQlClass() { result = "SingletonMethod" }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
final AstNode getBody() { ruby_singleton_method_body(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
final UnderscoreMethodName getName() { ruby_singleton_method_def(this, result, _) }
|
||||
|
||||
@@ -1530,15 +1557,12 @@ module Ruby {
|
||||
/** Gets the node corresponding to the field `parameters`. */
|
||||
final MethodParameters getParameters() { ruby_singleton_method_parameters(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
final AstNode getChild(int i) { ruby_singleton_method_child(this, i, result) }
|
||||
|
||||
/** Gets a field or child node of this node. */
|
||||
final override AstNode getAFieldOrChild() {
|
||||
ruby_singleton_method_body(this, result) or
|
||||
ruby_singleton_method_def(this, result, _) or
|
||||
ruby_singleton_method_def(this, _, result) or
|
||||
ruby_singleton_method_parameters(this, result) or
|
||||
ruby_singleton_method_child(this, _, result)
|
||||
ruby_singleton_method_parameters(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,18 +200,18 @@ private module Cached {
|
||||
or
|
||||
i = any(Ruby::Binary x).getRight()
|
||||
or
|
||||
i = any(Ruby::Block x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::BlockArgument x).getChild()
|
||||
or
|
||||
i = any(Ruby::BlockBody x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::BodyStatement x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::Call x).getReceiver()
|
||||
or
|
||||
i = any(Ruby::Case x).getValue()
|
||||
or
|
||||
i = any(Ruby::CaseMatch x).getValue()
|
||||
or
|
||||
i = any(Ruby::Class x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::Conditional x).getCondition()
|
||||
or
|
||||
i = any(Ruby::Conditional x).getConsequence()
|
||||
@@ -220,8 +220,6 @@ private module Cached {
|
||||
or
|
||||
i = any(Ruby::Do x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::DoBlock x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::ElementReference x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::ElementReference x).getObject()
|
||||
@@ -250,9 +248,7 @@ private module Cached {
|
||||
or
|
||||
i = any(Ruby::KeywordParameter x).getValue()
|
||||
or
|
||||
i = any(Ruby::Method x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::Module x).getChild(_)
|
||||
i = any(Ruby::Method x).getBody()
|
||||
or
|
||||
i = any(Ruby::OperatorAssignment x).getRight()
|
||||
or
|
||||
@@ -282,9 +278,7 @@ private module Cached {
|
||||
or
|
||||
i = any(Ruby::SingletonClass x).getValue()
|
||||
or
|
||||
i = any(Ruby::SingletonClass x).getChild(_)
|
||||
or
|
||||
i = any(Ruby::SingletonMethod x).getChild(_)
|
||||
i = any(Ruby::SingletonMethod x).getBody()
|
||||
or
|
||||
i = any(Ruby::SingletonMethod x).getObject()
|
||||
or
|
||||
|
||||
@@ -311,13 +311,13 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate immediatelyControls(ConditionBlock cb, BasicBlock succ, BooleanSuccessor s) {
|
||||
predicate immediatelyControls(ConditionBlock cb, BasicBlock succ, ConditionalSuccessor s) {
|
||||
succ = cb.getASuccessor(s) and
|
||||
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != cb | succ.dominates(pred))
|
||||
}
|
||||
|
||||
cached
|
||||
predicate controls(ConditionBlock cb, BasicBlock controlled, BooleanSuccessor s) {
|
||||
predicate controls(ConditionBlock cb, BasicBlock controlled, ConditionalSuccessor s) {
|
||||
exists(BasicBlock succ | cb.immediatelyControls(succ, s) | succ.dominates(controlled))
|
||||
}
|
||||
}
|
||||
@@ -406,7 +406,7 @@ class ConditionBlock extends BasicBlock {
|
||||
* successor of this block, and `succ` can only be reached from
|
||||
* the callable entry point by going via the `s` edge out of this basic block.
|
||||
*/
|
||||
predicate immediatelyControls(BasicBlock succ, BooleanSuccessor s) {
|
||||
predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
|
||||
immediatelyControls(this, succ, s)
|
||||
}
|
||||
|
||||
@@ -415,5 +415,7 @@ class ConditionBlock extends BasicBlock {
|
||||
* conditional value `s`. That is, `controlled` can only be reached from
|
||||
* the callable entry point by going via the `s` edge out of this basic block.
|
||||
*/
|
||||
predicate controls(BasicBlock controlled, BooleanSuccessor s) { controls(this, controlled, s) }
|
||||
predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
|
||||
controls(this, controlled, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ module ExprNodes {
|
||||
}
|
||||
|
||||
private class WhenClauseChildMapping extends NonExprChildMapping, WhenClause {
|
||||
override predicate relevantChild(AstNode e) { e = this.getBody() }
|
||||
override predicate relevantChild(AstNode e) { e = [this.getBody(), this.getAPattern()] }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `WhenClause` AST expression. */
|
||||
@@ -444,6 +444,9 @@ module ExprNodes {
|
||||
|
||||
/** Gets the body of this `when`-clause. */
|
||||
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
|
||||
|
||||
/** Gets the `i`th pattern this `when`-clause. */
|
||||
final ExprCfgNode getPattern(int i) { e.hasCfgChild(e.getPattern(i), this, result) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `CasePattern`. */
|
||||
@@ -711,6 +714,19 @@ module ExprNodes {
|
||||
final CfgNode getReceiver() { e.hasCfgChild(e.getReceiver(), this, result) }
|
||||
}
|
||||
|
||||
private class SelfVariableAccessMapping extends ExprChildMapping, SelfVariableAccess {
|
||||
override predicate relevantChild(AstNode n) { none() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `SelfVariableAccess` AST expression. */
|
||||
class SelfVariableAccessCfgNode extends ExprCfgNode {
|
||||
final override string getAPrimaryQlClass() { result = "SelfVariableAccessCfgNode" }
|
||||
|
||||
override SelfVariableAccessMapping e;
|
||||
|
||||
override SelfVariableAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `VariableWriteAccess` AST expression. */
|
||||
class VariableWriteAccessCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "VariableWriteAccessCfgNode" }
|
||||
|
||||
@@ -44,7 +44,7 @@ class CfgNode extends TCfgNode {
|
||||
final File getFile() { result = this.getLocation().getFile() }
|
||||
|
||||
/** Holds if this control flow node has conditional successors. */
|
||||
final predicate isCondition() { exists(this.getASuccessor(any(BooleanSuccessor bs))) }
|
||||
final predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor bs))) }
|
||||
|
||||
/** Gets the scope of this node. */
|
||||
final CfgScope getScope() { result = getNodeCfgScope(this) }
|
||||
|
||||
@@ -13,13 +13,9 @@ private import codeql.ruby.Frameworks
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `RemoteFlowSource::Range` instead.
|
||||
*/
|
||||
class RemoteFlowSource extends DataFlow::Node {
|
||||
RemoteFlowSource::Range self;
|
||||
|
||||
RemoteFlowSource() { this = self }
|
||||
|
||||
class RemoteFlowSource extends DataFlow::Node instanceof RemoteFlowSource::Range {
|
||||
/** Gets a string that describes the type of this remote flow source. */
|
||||
string getSourceType() { result = self.getSourceType() }
|
||||
string getSourceType() { result = super.getSourceType() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new sources of remote user input. */
|
||||
|
||||
@@ -9,12 +9,11 @@ module Ssa {
|
||||
private import codeql.Locations
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.ast.Variable
|
||||
private import internal.SsaImplCommon as SsaImplCommon
|
||||
private import internal.SsaImpl as SsaImpl
|
||||
private import CfgNodes::ExprNodes
|
||||
|
||||
/** A static single assignment (SSA) definition. */
|
||||
class Definition extends SsaImplCommon::Definition {
|
||||
class Definition extends SsaImpl::Definition {
|
||||
/**
|
||||
* Gets the control flow node of this SSA definition, if any. Phi nodes are
|
||||
* examples of SSA definitions without a control flow node, as they are
|
||||
@@ -190,7 +189,7 @@ module Ssa {
|
||||
* puts x
|
||||
* ```
|
||||
*/
|
||||
class WriteDefinition extends Definition, SsaImplCommon::WriteDefinition {
|
||||
class WriteDefinition extends Definition, SsaImpl::WriteDefinition {
|
||||
private VariableWriteAccess write;
|
||||
|
||||
WriteDefinition() {
|
||||
@@ -223,7 +222,7 @@ module Ssa {
|
||||
/**
|
||||
* An SSA definition that corresponds to the value of `self` upon entry to a method, class or module.
|
||||
*/
|
||||
class SelfDefinition extends Definition, SsaImplCommon::WriteDefinition {
|
||||
class SelfDefinition extends Definition, SsaImpl::WriteDefinition {
|
||||
private SelfVariable v;
|
||||
|
||||
SelfDefinition() {
|
||||
@@ -254,7 +253,7 @@ module Ssa {
|
||||
* since the assignment to `x` is conditional, an unitialized definition for
|
||||
* `x` is inserted at the start of `m`.
|
||||
*/
|
||||
class UninitializedDefinition extends Definition, SsaImplCommon::WriteDefinition {
|
||||
class UninitializedDefinition extends Definition, SsaImpl::WriteDefinition {
|
||||
UninitializedDefinition() {
|
||||
exists(BasicBlock bb, int i, Variable v |
|
||||
this.definesAt(v, bb, i) and
|
||||
@@ -283,7 +282,7 @@ module Ssa {
|
||||
*
|
||||
* an entry definition for `y` is inserted at the start of the `do` block.
|
||||
*/
|
||||
class CapturedEntryDefinition extends Definition, SsaImplCommon::WriteDefinition {
|
||||
class CapturedEntryDefinition extends Definition, SsaImpl::WriteDefinition {
|
||||
CapturedEntryDefinition() {
|
||||
exists(BasicBlock bb, int i, Variable v |
|
||||
this.definesAt(v, bb, i) and
|
||||
@@ -312,7 +311,7 @@ module Ssa {
|
||||
*
|
||||
* a definition for `y` is inserted at the call to `times`.
|
||||
*/
|
||||
class CapturedCallDefinition extends Definition, SsaImplCommon::UncertainWriteDefinition {
|
||||
class CapturedCallDefinition extends Definition, SsaImpl::UncertainWriteDefinition {
|
||||
CapturedCallDefinition() {
|
||||
exists(Variable v, BasicBlock bb, int i |
|
||||
this.definesAt(v, bb, i) and
|
||||
@@ -343,7 +342,7 @@ module Ssa {
|
||||
*
|
||||
* a phi node for `x` is inserted just before the call `puts x`.
|
||||
*/
|
||||
class PhiNode extends Definition, SsaImplCommon::PhiNode {
|
||||
class PhiNode extends Definition, SsaImpl::PhiNode {
|
||||
/**
|
||||
* Gets an input of this phi node.
|
||||
*
|
||||
|
||||
@@ -2,10 +2,12 @@ private import ruby
|
||||
private import codeql.ruby.CFG
|
||||
private import DataFlowPrivate
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
newtype TReturnKind =
|
||||
TNormalReturnKind() or
|
||||
@@ -148,12 +150,19 @@ private class NormalCall extends DataFlowCall, TNormalCall {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate methodCall(
|
||||
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node receiver, string method
|
||||
) {
|
||||
method = call.getExpr().(MethodCall).getMethodName() and
|
||||
receiver.asExpr() = call.getReceiver()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowsToMethodCall(
|
||||
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode, string method
|
||||
) {
|
||||
exists(DataFlow::Node nodeTo |
|
||||
method = call.getExpr().(MethodCall).getMethodName() and
|
||||
nodeTo.asExpr() = call.getReceiver() and
|
||||
sourceNode.flowsTo(nodeTo)
|
||||
exists(DataFlow::Node receiver |
|
||||
methodCall(call, receiver, method) and
|
||||
sourceNode.flowsTo(receiver)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -177,9 +186,94 @@ private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superC
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate instanceMethodCall(CfgNodes::ExprNodes::CallCfgNode call, Module tp, string method) {
|
||||
exists(DataFlow::LocalSourceNode sourceNode |
|
||||
methodCall(call, sourceNode, method) and
|
||||
sourceNode = trackInstance(tp)
|
||||
exists(DataFlow::Node receiver, Module m, boolean exact |
|
||||
methodCall(call, receiver, method) and
|
||||
receiver = trackInstance(m, exact)
|
||||
|
|
||||
tp = m
|
||||
or
|
||||
// When we don't know the exact type, it could be any sub class
|
||||
exact = false and
|
||||
tp.getSuperClass+() = m
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `self` belongs to module `m`. */
|
||||
pragma[nomagic]
|
||||
private predicate selfInModule(SelfVariable self, Module m) {
|
||||
exists(Scope scope |
|
||||
scope = self.getDeclaringScope() and
|
||||
m = scope.(ModuleBase).getModule() and
|
||||
not scope instanceof Toplevel
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `self` belongs to method `method` inside module `m`. */
|
||||
pragma[nomagic]
|
||||
private predicate selfInMethod(SelfVariable self, MethodBase method, Module m) {
|
||||
exists(ModuleBase encl |
|
||||
method = self.getDeclaringScope() and
|
||||
encl = method.getEnclosingModule() and
|
||||
if encl instanceof SingletonClass
|
||||
then m = encl.getEnclosingModule().getModule()
|
||||
else m = encl.getModule()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `self` belongs to the top-level. */
|
||||
pragma[nomagic]
|
||||
private predicate selfInToplevel(SelfVariable self, Module m) {
|
||||
self.getDeclaringScope() instanceof Toplevel and
|
||||
m = TResolved("Object")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if SSA definition `def` belongs to a variable introduced via pattern
|
||||
* matching on type `m`. For example, in
|
||||
*
|
||||
* ```rb
|
||||
* case object
|
||||
* in C => c then c.foo
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the SSA definition for `c` is introduced by matching on `C`.
|
||||
*/
|
||||
private predicate asModulePattern(SsaDefinitionNode def, Module m) {
|
||||
exists(AsPattern ap |
|
||||
m = resolveConstantReadAccess(ap.getPattern()) and
|
||||
def.getDefinition().(Ssa::WriteDefinition).getWriteAccess() = ap.getVariableAccess()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `read1` and `read2` are adjacent reads of SSA definition `def`,
|
||||
* and `read2` is checked to have type `m`. For example, in
|
||||
*
|
||||
* ```rb
|
||||
* case object
|
||||
* when C then object.foo
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the two reads of `object` are adjacent, and the second is checked to have type `C`.
|
||||
*/
|
||||
private predicate hasAdjacentTypeCheckedReads(
|
||||
Ssa::Definition def, CfgNodes::ExprCfgNode read1, CfgNodes::ExprCfgNode read2, Module m
|
||||
) {
|
||||
exists(
|
||||
CfgNodes::ExprCfgNode pattern, ConditionBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case
|
||||
|
|
||||
m = resolveConstantReadAccess(pattern.getExpr()) and
|
||||
cb.getLastNode() = pattern and
|
||||
cb.controls(read2.getBasicBlock(),
|
||||
any(SuccessorTypes::MatchingSuccessor match | match.getValue() = true)) and
|
||||
def.hasAdjacentReads(read1, read2) and
|
||||
case.getValue() = read1
|
||||
|
|
||||
pattern = case.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_)
|
||||
or
|
||||
pattern = case.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -222,9 +316,45 @@ private module Cached {
|
||||
else any()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::LocalSourceNode sourceNode |
|
||||
methodCall(call, sourceNode, method) and
|
||||
sourceNode = trackSingletonMethod(result, method)
|
||||
// singleton method defined on an instance, e.g.
|
||||
// ```rb
|
||||
// c = C.new
|
||||
// def c.singleton; end # <- result
|
||||
// c.singleton # <- call
|
||||
// ```
|
||||
exists(DataFlow::Node receiver |
|
||||
methodCall(call, receiver, method) and
|
||||
receiver = trackSingletonMethodOnInstance(result, method)
|
||||
)
|
||||
or
|
||||
// singleton method defined on a module
|
||||
exists(DataFlow::Node sourceNode, Module m |
|
||||
flowsToMethodCall(call, sourceNode, method) and
|
||||
singletonMethodOnModule(result, method, m)
|
||||
|
|
||||
// ```rb
|
||||
// def C.singleton; end # <- result
|
||||
// C.singleton # <- call
|
||||
// ```
|
||||
sourceNode = trackModuleAccess(m)
|
||||
or
|
||||
// ```rb
|
||||
// class C
|
||||
// def self.singleton; end # <- result
|
||||
// self.singleton # <- call
|
||||
// end
|
||||
// ```
|
||||
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m)
|
||||
or
|
||||
// ```rb
|
||||
// class C
|
||||
// def self.singleton; end # <- result
|
||||
// def self.other
|
||||
// self.singleton # <- call
|
||||
// end
|
||||
// end
|
||||
// ```
|
||||
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), _, m.getSuperClass*())
|
||||
)
|
||||
)
|
||||
or
|
||||
@@ -293,73 +423,166 @@ private module Cached {
|
||||
|
||||
import Cached
|
||||
|
||||
private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result.asExpr().getExpr() instanceof NilLiteral and tp = TResolved("NilClass")
|
||||
or
|
||||
result.asExpr().getExpr().(BooleanLiteral).isFalse() and tp = TResolved("FalseClass")
|
||||
or
|
||||
result.asExpr().getExpr().(BooleanLiteral).isTrue() and tp = TResolved("TrueClass")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof IntegerLiteral and tp = TResolved("Integer")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof FloatLiteral and tp = TResolved("Float")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof RationalLiteral and tp = TResolved("Rational")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof ComplexLiteral and tp = TResolved("Complex")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof StringlikeLiteral and tp = TResolved("String")
|
||||
or
|
||||
result.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and tp = TResolved("Array")
|
||||
or
|
||||
result.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and tp = TResolved("Hash")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof MethodBase and tp = TResolved("Symbol")
|
||||
or
|
||||
result.asParameter() instanceof BlockParameter and tp = TResolved("Proc")
|
||||
or
|
||||
result.asExpr().getExpr() instanceof Lambda and tp = TResolved("Proc")
|
||||
or
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node nodeTo |
|
||||
call.getExpr().(MethodCall).getMethodName() = "new" and
|
||||
nodeTo.asExpr() = call.getReceiver() and
|
||||
trackModule(tp).flowsTo(nodeTo) and
|
||||
result.asExpr() = call
|
||||
)
|
||||
or
|
||||
// `self` in method
|
||||
tp = result.(SsaSelfDefinitionNode).getSelfScope().(Method).getEnclosingModule().getModule()
|
||||
or
|
||||
// `self` in singleton method
|
||||
flowsToSingletonMethodObject(trackInstance(tp), result.(SsaSelfDefinitionNode).getSelfScope())
|
||||
or
|
||||
// `self` in top-level
|
||||
result.(SsaSelfDefinitionNode).getSelfScope() instanceof Toplevel and
|
||||
tp = TResolved("Object")
|
||||
or
|
||||
// a module or class
|
||||
exists(Module m |
|
||||
result = trackModule(m) and
|
||||
if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")
|
||||
)
|
||||
)
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) {
|
||||
t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackInstanceRec(tp, t2, summary) and t = t2.append(summary)
|
||||
result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackInstanceRec(Module tp, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackInstance(tp, t), result, summary)
|
||||
private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackModuleAccess(m, t), result, summary)
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackInstance(Module tp) {
|
||||
result = trackInstance(tp, TypeTracker::end())
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
|
||||
result = trackModuleAccess(m, TypeTracker::end())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result.asExpr().getExpr() instanceof NilLiteral and
|
||||
tp = TResolved("NilClass") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
tp = TResolved("FalseClass") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr().(BooleanLiteral).isTrue() and
|
||||
tp = TResolved("TrueClass") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof IntegerLiteral and
|
||||
tp = TResolved("Integer") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof FloatLiteral and
|
||||
tp = TResolved("Float") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof RationalLiteral and
|
||||
tp = TResolved("Rational") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof ComplexLiteral and
|
||||
tp = TResolved("Complex") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof StringlikeLiteral and
|
||||
tp = TResolved("String") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
|
||||
tp = TResolved("Array") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
|
||||
tp = TResolved("Hash") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof MethodBase and
|
||||
tp = TResolved("Symbol") and
|
||||
exact = true
|
||||
or
|
||||
result.asParameter() instanceof BlockParameter and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
result.asExpr().getExpr() instanceof Lambda and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode |
|
||||
flowsToMethodCall(call, sourceNode, "new") and
|
||||
exact = true and
|
||||
result.asExpr() = call
|
||||
|
|
||||
// `C.new`
|
||||
sourceNode = trackModuleAccess(tp)
|
||||
or
|
||||
// `self.new` inside a module
|
||||
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), tp)
|
||||
or
|
||||
// `self.new` inside a singleton method
|
||||
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), tp)
|
||||
)
|
||||
or
|
||||
// `self` reference in method or top-level (but not in module or singleton method,
|
||||
// where instance methods cannot be called; only singleton methods)
|
||||
result =
|
||||
any(SsaSelfDefinitionNode self |
|
||||
exists(MethodBase m |
|
||||
selfInMethod(self.getVariable(), m, tp) and
|
||||
not m instanceof SingletonMethod and
|
||||
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
|
||||
)
|
||||
or
|
||||
selfInToplevel(self.getVariable(), tp) and
|
||||
exact = true
|
||||
)
|
||||
or
|
||||
exists(Module m |
|
||||
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
|
||||
exact = true
|
||||
|
|
||||
// needed for e.g. `C.new`
|
||||
m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
// needed for e.g. `self.include`
|
||||
selfInModule(result.(SsaSelfDefinitionNode).getVariable(), m)
|
||||
or
|
||||
// needed for e.g. `self.puts`
|
||||
selfInMethod(result.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), m)
|
||||
)
|
||||
or
|
||||
// `in C => c then c.foo`
|
||||
asModulePattern(result, tp) and
|
||||
exact = false
|
||||
or
|
||||
// `case object when C then object.foo`
|
||||
hasAdjacentTypeCheckedReads(_, _, result.asExpr(), tp) and
|
||||
exact = false
|
||||
)
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackInstanceRec(tp, t2, exact, summary) and t = t2.append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
localFlowStepTypeTracker(nodeFrom, nodeTo) and
|
||||
summary.toString() = "level"
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters and type checked variables. For those,
|
||||
* we instead rely on the type of the enclosing module resp. the type being checked
|
||||
* against, and apply an open-world assumption when determining possible dispatch
|
||||
* targets.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) |
|
||||
StepSummary::smallstep(mid, result, summary)
|
||||
or
|
||||
localFlowStep(mid, result, summary)
|
||||
) and
|
||||
not result instanceof SelfParameterNode and
|
||||
not hasAdjacentTypeCheckedReads(_, _, result.asExpr(), _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstance(Module tp, boolean exact) {
|
||||
result = trackInstance(tp, exact, TypeTracker::end())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
|
||||
t.start() and result.asExpr().getExpr() = block
|
||||
or
|
||||
@@ -373,84 +596,190 @@ private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, Step
|
||||
StepSummary::step(trackBlock(block, t), result, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlock(Block block) {
|
||||
result = trackBlock(block, TypeTracker::end())
|
||||
}
|
||||
|
||||
private predicate singletonMethod(MethodBase method, Expr object) {
|
||||
object = method.(SingletonMethod).getObject()
|
||||
or
|
||||
exists(SingletonClass cls |
|
||||
object = cls.getValue() and method instanceof Method and method = cls.getAMethod()
|
||||
/** Holds if `m` is a singleton method named `name`, defined on `object. */
|
||||
private predicate singletonMethod(MethodBase m, string name, Expr object) {
|
||||
name = m.getName() and
|
||||
(
|
||||
object = m.(SingletonMethod).getObject()
|
||||
or
|
||||
m = any(SingletonClass cls | object = cls.getValue()).getAMethod().(Method)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowsToSingletonMethodObject(DataFlow::LocalSourceNode nodeFrom, MethodBase method) {
|
||||
private predicate flowsToSingletonMethodObject(
|
||||
DataFlow::LocalSourceNode nodeFrom, MethodBase m, string name
|
||||
) {
|
||||
exists(DataFlow::Node nodeTo |
|
||||
nodeFrom.flowsTo(nodeTo) and
|
||||
singletonMethod(method, nodeTo.asExpr().getExpr())
|
||||
singletonMethod(m, name, nodeTo.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `method` is a singleton method named `name`, defined on module
|
||||
* `m`:
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def self.m1; end # included
|
||||
*
|
||||
* class << self
|
||||
* def m2; end # included
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* def C.m3; end # included
|
||||
*
|
||||
* c_alias = C
|
||||
* def c_alias.m4; end # included
|
||||
*
|
||||
* c = C.new
|
||||
* def c.m5; end # not included
|
||||
*
|
||||
* class << c
|
||||
* def m6; end # not included
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate moduleFlowsToSingletonMethodObject(Module m, MethodBase method) {
|
||||
flowsToSingletonMethodObject(trackModule(m), method)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackSingletonMethod0(MethodBase method, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
flowsToSingletonMethodObject(result, method)
|
||||
or
|
||||
exists(Module m | result = trackModule(m) and moduleFlowsToSingletonMethodObject(m, method))
|
||||
private predicate singletonMethodOnModule(MethodBase method, string name, Module m) {
|
||||
exists(Expr object |
|
||||
singletonMethod(method, name, object) and
|
||||
selfInModule(object.(SelfVariableReadAccess).getVariable(), m)
|
||||
)
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackSingletonMethod0Rec(method, t2, summary) and t = t2.append(summary)
|
||||
)
|
||||
flowsToSingletonMethodObject(trackModuleAccess(m), method, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `method` is a singleton method named `name`, defined on expression
|
||||
* `object`, where `object` is not likely to resolve to a module:
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def self.m1; end # not included
|
||||
*
|
||||
* class << self
|
||||
* def m2; end # not included
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* def C.m3; end # not included
|
||||
*
|
||||
* c_alias = C
|
||||
* def c_alias.m4; end # included (due to negative recursion limitation)
|
||||
*
|
||||
* c = C.new
|
||||
* def c.m5; end # included
|
||||
*
|
||||
* class << c
|
||||
* def m6; end # included
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackSingletonMethod0Rec(
|
||||
MethodBase method, TypeTracker t, StepSummary summary
|
||||
predicate singletonMethodOnInstance(MethodBase method, string name, Expr object) {
|
||||
singletonMethod(method, name, object) and
|
||||
not selfInModule(object.(SelfVariableReadAccess).getVariable(), _) and
|
||||
// cannot use `trackModuleAccess` because of negative recursion
|
||||
not exists(resolveConstantReadAccess(object))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
|
||||
*
|
||||
* This is only used for tracking singleton methods, where we want to be able
|
||||
* to handle cases like
|
||||
*
|
||||
* ```rb
|
||||
* def add_singleton x
|
||||
* def x.foo; end
|
||||
* end
|
||||
*
|
||||
* y = add_singleton C.new
|
||||
* y.foo
|
||||
* ```
|
||||
*
|
||||
* and
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def add_singleton_to_self
|
||||
* def self.foo; end
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* y = C.new
|
||||
* y.add_singleton_to_self
|
||||
* y.foo
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
StepSummary::step(trackSingletonMethod0(method, t), result, summary)
|
||||
exists(
|
||||
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node arg, DataFlow::ParameterNode p,
|
||||
Expr nodeFromPreExpr
|
||||
|
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
or
|
||||
nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackSingletonMethod(MethodBase m, string name) {
|
||||
result = trackSingletonMethod0(m, TypeTracker::end()) and
|
||||
name = m.getName()
|
||||
}
|
||||
|
||||
private SsaSelfDefinitionNode selfInModule(Module tp) {
|
||||
tp = result.getSelfScope().(ModuleBase).getModule()
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackModule(Module tp, TypeTracker t) {
|
||||
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
// ConstantReadAccess to Module
|
||||
resolveConstantReadAccess(result.asExpr().getExpr()) = tp
|
||||
or
|
||||
// `self` reference to Module
|
||||
result = selfInModule(tp)
|
||||
)
|
||||
singletonMethodOnInstance(method, name, result.asExpr().getExpr())
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackModuleRec(tp, t2, summary) and t = t2.append(summary)
|
||||
result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and
|
||||
t = t2.append(summary) and
|
||||
// Stop flow at redefinitions.
|
||||
//
|
||||
// Example:
|
||||
// ```rb
|
||||
// def x.foo; end
|
||||
// def x.foo; end
|
||||
// x.foo # <- we want to resolve this call to the second definition only
|
||||
// ```
|
||||
not singletonMethodOnInstance(_, name, result.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleRec(Module tp, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackModule(tp, t), result, summary)
|
||||
private DataFlow::Node trackSingletonMethodOnInstanceRec(
|
||||
MethodBase method, string name, TypeTracker t, StepSummary summary
|
||||
) {
|
||||
exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) |
|
||||
StepSummary::smallstep(mid, result, summary)
|
||||
or
|
||||
paramReturnFlow(mid, result, summary)
|
||||
or
|
||||
localFlowStep(mid, result, summary)
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackModule(Module tp) {
|
||||
result = trackModule(tp, TypeTracker::end())
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) {
|
||||
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,6 +894,12 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) { not ppos.isSelf() }
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) { not apos.isSelf() }
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[nomagic]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
@@ -582,9 +917,9 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
ppos.isHashSplat() and apos.isHashSplat()
|
||||
or
|
||||
ppos.isAny() and not apos.isSelf()
|
||||
ppos.isAny() and argumentPositionIsNotSelf(apos)
|
||||
or
|
||||
apos.isAny() and not ppos.isSelf()
|
||||
apos.isAny() and parameterPositionIsNotSelf(ppos)
|
||||
or
|
||||
ppos.isAnyNamed() and apos.isKeyword(_)
|
||||
or
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -203,6 +203,12 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `n` is not a constant expression. */
|
||||
predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) {
|
||||
not exists(n.getConstantValue()) and
|
||||
not n.getExpr() instanceof ConstantAccess
|
||||
}
|
||||
|
||||
/** A collection of cached types and predicates to be evaluated in the same stage. */
|
||||
cached
|
||||
private module Cached {
|
||||
@@ -232,8 +238,12 @@ private module Cached {
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
|
||||
} or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
|
||||
n instanceof Argument or
|
||||
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
|
||||
// filter out nodes that clearly don't need post-update nodes
|
||||
isNonConstantExpr(n) and
|
||||
(
|
||||
n instanceof Argument or
|
||||
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
|
||||
)
|
||||
} or
|
||||
TSummaryNode(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c,
|
||||
@@ -438,6 +448,9 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
|
||||
/** Gets the underlying SSA definition. */
|
||||
Ssa::Definition getDefinition() { result = def }
|
||||
|
||||
/** Gets the underlying variable. */
|
||||
Variable getVariable() { result = def.getSourceVariable() }
|
||||
|
||||
override CfgScope getCfgScope() { result = def.getBasicBlock().getScope() }
|
||||
|
||||
override Location getLocationImpl() { result = def.getLocation() }
|
||||
@@ -539,6 +552,9 @@ private module ParameterNodes {
|
||||
|
||||
final MethodBase getMethod() { result = method }
|
||||
|
||||
/** Gets the underlying `self` variable. */
|
||||
final SelfVariable getSelfVariable() { result.getDeclaringScope() = method }
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
@@ -613,7 +629,7 @@ private module ParameterNodes {
|
||||
* where direct keyword matching is possible, since we construct a synthesized hash
|
||||
* splat argument (`SynthHashSplatArgumentNode`) at the call site, which means that
|
||||
* `taint(1)` will flow into `p1` both via normal keyword matching and via the synthesized
|
||||
* nodes (and similarly for `p2`). However, this redunancy is OK since
|
||||
* nodes (and similarly for `p2`). However, this redundancy is OK since
|
||||
* (a) it means that type-tracking through keyword arguments also works in most cases,
|
||||
* (b) read/store steps can be avoided when direct keyword matching is possible, and
|
||||
* hence access path limits are not a concern, and
|
||||
@@ -1036,7 +1052,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
// (instance variable assignment or setter method call).
|
||||
node2.asExpr() =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
|
||||
node1.asExpr() = call.getReceiver() and
|
||||
node1.asExpr() =
|
||||
any(CfgNodes::ExprCfgNode e | e = call.getReceiver() and isNonConstantExpr(e)) and
|
||||
call.getNumberOfArguments() = 0 and
|
||||
c.isSingleton(any(Content::FieldContent ct |
|
||||
ct.getName() = "@" + call.getExpr().getMethodName()
|
||||
|
||||
@@ -87,6 +87,41 @@ class CallNode extends LocalSourceNode, ExprNode {
|
||||
|
||||
/** Gets the block of this call. */
|
||||
Node getBlock() { result.asExpr() = node.getBlock() }
|
||||
|
||||
/**
|
||||
* Gets the data-flow node corresponding to the named argument of the call
|
||||
* corresponding to this data-flow node, also including values passed with (pre Ruby
|
||||
* 2.0) hash arguments.
|
||||
*
|
||||
* Such hash arguments are tracked back to their source location within functions, but
|
||||
* no inter-procedural analysis occurs.
|
||||
*
|
||||
* This means all 3 variants below will be handled by this predicate:
|
||||
*
|
||||
* ```ruby
|
||||
* foo(..., some_option: 42)
|
||||
* foo(..., { some_option: 42 })
|
||||
* options = { some_option: 42 }
|
||||
* foo(..., options)
|
||||
* ```
|
||||
*/
|
||||
Node getKeywordArgumentIncludeHashArgument(string name) {
|
||||
// to reduce number of computed tuples, I have put bindingset on both this and name,
|
||||
// meaning we only do the local backwards tracking for known calls and known names.
|
||||
// (not because a performance problem was seen, it just seemed right).
|
||||
result = this.getKeywordArgument(name)
|
||||
or
|
||||
exists(CfgNodes::ExprNodes::PairCfgNode pair |
|
||||
pair =
|
||||
this.getArgument(_)
|
||||
.getALocalSource()
|
||||
.asExpr()
|
||||
.(CfgNodes::ExprNodes::HashLiteralCfgNode)
|
||||
.getAKeyValuePair() and
|
||||
pair.getKey().getConstantValue().isStringlikeValue(name) and
|
||||
result.asExpr() = pair.getValue()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -226,15 +226,6 @@ module Public {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside `content` are cleared on objects passed as
|
||||
* arguments at position `pos` to this callable.
|
||||
*
|
||||
* TODO: Remove once all languages support `WithoutContent` tokens.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate clearsContent(ParameterPosition pos, ContentSet content) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the summary is auto generated.
|
||||
*/
|
||||
@@ -328,23 +319,6 @@ module Private {
|
||||
SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and
|
||||
preservesValue = preservesValue1.booleanAnd(preservesValue2)
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition ppos, ContentSet cs |
|
||||
c.clearsContent(ppos, cs) and
|
||||
input = SummaryComponentStack::push(SummaryComponent::withoutContent(cs), output) and
|
||||
output = SummaryComponentStack::argument(ppos) and
|
||||
preservesValue = true
|
||||
)
|
||||
}
|
||||
|
||||
private class MkClearStack extends RequiredSummaryComponentStack {
|
||||
override predicate required(SummaryComponent head, SummaryComponentStack tail) {
|
||||
exists(SummarizedCallable sc, ParameterPosition ppos, ContentSet cs |
|
||||
sc.clearsContent(ppos, cs) and
|
||||
head = SummaryComponent::withoutContent(cs) and
|
||||
tail = SummaryComponentStack::argument(ppos)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -945,8 +919,7 @@ module Private {
|
||||
AccessPath inSpec, AccessPath outSpec, string kind
|
||||
) {
|
||||
summaryElement(this, inSpec, outSpec, kind, true) and
|
||||
not summaryElement(this, _, _, _, false) and
|
||||
not this.clearsContent(_, _)
|
||||
not summaryElement(this, _, _, _, false)
|
||||
}
|
||||
|
||||
private predicate relevantSummaryElement(AccessPath inSpec, AccessPath outSpec, string kind) {
|
||||
|
||||
@@ -1,19 +1,72 @@
|
||||
private import SsaImplCommon
|
||||
private import SsaImplSpecific as SsaImplSpecific
|
||||
private import codeql.ssa.Ssa as SsaImplCommon
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.CFG as CFG
|
||||
private import codeql.ruby.ast.Variable
|
||||
private import CfgNodes::ExprNodes
|
||||
private import CFG::CfgNodes::ExprNodes
|
||||
|
||||
private module SsaInput implements SsaImplCommon::InputSig {
|
||||
private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks
|
||||
|
||||
class BasicBlock = BasicBlocks::BasicBlock;
|
||||
|
||||
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() }
|
||||
|
||||
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
|
||||
|
||||
class ExitBasicBlock = BasicBlocks::ExitBasicBlock;
|
||||
|
||||
class SourceVariable = LocalVariable;
|
||||
|
||||
/**
|
||||
* Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`.
|
||||
* `certain` is true if the write definitely occurs.
|
||||
*/
|
||||
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
|
||||
(
|
||||
exists(Scope scope | scope = v.(SelfVariable).getDeclaringScope() |
|
||||
// We consider the `self` variable to have a single write at the entry to a method block...
|
||||
scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and
|
||||
i = 0
|
||||
or
|
||||
// ...or a class or module block.
|
||||
bb.getNode(i).getNode() = scope.(ModuleBase).getAControlFlowEntryNode()
|
||||
)
|
||||
or
|
||||
uninitializedWrite(bb, i, v)
|
||||
or
|
||||
capturedEntryWrite(bb, i, v)
|
||||
or
|
||||
variableWriteActual(bb, i, v, _)
|
||||
) and
|
||||
certain = true
|
||||
or
|
||||
capturedCallWrite(_, bb, i, v) and
|
||||
certain = false
|
||||
}
|
||||
|
||||
predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) {
|
||||
variableReadActual(bb, i, v) and
|
||||
certain = true
|
||||
or
|
||||
capturedCallRead(_, bb, i, v) and
|
||||
certain = false
|
||||
or
|
||||
capturedExitRead(bb, i, v) and
|
||||
certain = false
|
||||
}
|
||||
}
|
||||
|
||||
import SsaImplCommon::Make<SsaInput>
|
||||
|
||||
/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */
|
||||
predicate uninitializedWrite(EntryBasicBlock bb, int i, LocalVariable v) {
|
||||
predicate uninitializedWrite(CFG::EntryBasicBlock bb, int i, LocalVariable v) {
|
||||
v.getDeclaringScope() = bb.getScope() and
|
||||
i = -1
|
||||
}
|
||||
|
||||
/** Holds if `bb` contains a caputured read of variable `v`. */
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) {
|
||||
private predicate hasCapturedVariableRead(CFG::BasicBlock bb, LocalVariable v) {
|
||||
exists(LocalVariableReadAccess read |
|
||||
read = bb.getANode().getNode() and
|
||||
read.isCapturedAccess() and
|
||||
@@ -23,7 +76,7 @@ private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) {
|
||||
|
||||
/** Holds if `bb` contains a caputured write to variable `v`. */
|
||||
pragma[noinline]
|
||||
private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) {
|
||||
private predicate writesCapturedVariable(CFG::BasicBlock bb, LocalVariable v) {
|
||||
exists(LocalVariableWriteAccess write |
|
||||
write = bb.getANode().getNode() and
|
||||
write.isCapturedAccess() and
|
||||
@@ -35,7 +88,7 @@ private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) {
|
||||
* Holds if a pseudo read of captured variable `v` should be inserted
|
||||
* at index `i` in exit block `bb`.
|
||||
*/
|
||||
private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVariable v) {
|
||||
private predicate capturedExitRead(CFG::AnnotatedExitBasicBlock bb, int i, LocalVariable v) {
|
||||
bb.isNormal() and
|
||||
writesCapturedVariable(bb.getAPredecessor*(), v) and
|
||||
i = bb.length()
|
||||
@@ -46,7 +99,7 @@ private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVaria
|
||||
* or inside a (transitively) nested scope of `scope`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedRead(Variable v, CfgScope scope) {
|
||||
private predicate hasCapturedRead(Variable v, CFG::CfgScope scope) {
|
||||
any(LocalVariableReadAccess read |
|
||||
read.getVariable() = v and scope = read.getCfgScope().getOuterCfgScope*()
|
||||
).isCapturedAccess()
|
||||
@@ -57,13 +110,15 @@ private predicate hasCapturedRead(Variable v, CfgScope scope) {
|
||||
* outer scope of `scope`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate variableWriteInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) {
|
||||
SsaImplSpecific::variableWrite(bb, _, v, _) and
|
||||
private predicate variableWriteInOuterScope(CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope) {
|
||||
SsaInput::variableWrite(bb, _, v, _) and
|
||||
scope.getOuterCfgScope() = bb.getScope()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable v, CfgScope scope) {
|
||||
private predicate hasVariableWriteWithCapturedRead(
|
||||
CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope
|
||||
) {
|
||||
hasCapturedRead(v, scope) and
|
||||
variableWriteInOuterScope(bb, v, scope)
|
||||
}
|
||||
@@ -72,10 +127,8 @@ private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable
|
||||
* Holds if the call `call` at index `i` in basic block `bb` may reach
|
||||
* a callable that reads captured variable `v`.
|
||||
*/
|
||||
private predicate capturedCallRead(
|
||||
CfgNodes::ExprNodes::CallCfgNode call, BasicBlock bb, int i, LocalVariable v
|
||||
) {
|
||||
exists(CfgScope scope |
|
||||
private predicate capturedCallRead(CallCfgNode call, CFG::BasicBlock bb, int i, LocalVariable v) {
|
||||
exists(CFG::CfgScope scope |
|
||||
hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and
|
||||
call = bb.getNode(i)
|
||||
|
|
||||
@@ -88,30 +141,19 @@ private predicate capturedCallRead(
|
||||
}
|
||||
|
||||
/** Holds if `v` is read at index `i` in basic block `bb`. */
|
||||
private predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) {
|
||||
private predicate variableReadActual(CFG::BasicBlock bb, int i, LocalVariable v) {
|
||||
exists(VariableReadAccess read |
|
||||
read.getVariable() = v and
|
||||
read = bb.getNode(i).getNode()
|
||||
)
|
||||
}
|
||||
|
||||
predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) {
|
||||
variableReadActual(bb, i, v) and
|
||||
certain = true
|
||||
or
|
||||
capturedCallRead(_, bb, i, v) and
|
||||
certain = false
|
||||
or
|
||||
capturedExitRead(bb, i, v) and
|
||||
certain = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if captured variable `v` is written directly inside `scope`,
|
||||
* or inside a (transitively) nested scope of `scope`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedWrite(Variable v, CfgScope scope) {
|
||||
private predicate hasCapturedWrite(Variable v, CFG::CfgScope scope) {
|
||||
any(LocalVariableWriteAccess write |
|
||||
write.getVariable() = v and scope = write.getCfgScope().getOuterCfgScope*()
|
||||
).isCapturedAccess()
|
||||
@@ -122,13 +164,17 @@ private predicate hasCapturedWrite(Variable v, CfgScope scope) {
|
||||
* outer scope of `scope`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate variableReadActualInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) {
|
||||
private predicate variableReadActualInOuterScope(
|
||||
CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope
|
||||
) {
|
||||
variableReadActual(bb, _, v) and
|
||||
bb.getScope() = scope.getOuterCfgScope()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) {
|
||||
private predicate hasVariableReadWithCapturedWrite(
|
||||
CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope
|
||||
) {
|
||||
hasCapturedWrite(v, scope) and
|
||||
variableReadActualInOuterScope(bb, v, scope)
|
||||
}
|
||||
@@ -140,7 +186,7 @@ private module Cached {
|
||||
* `i` in entry block `bb`.
|
||||
*/
|
||||
cached
|
||||
predicate capturedEntryWrite(EntryBasicBlock bb, int i, LocalVariable v) {
|
||||
predicate capturedEntryWrite(CFG::EntryBasicBlock bb, int i, LocalVariable v) {
|
||||
hasCapturedVariableRead(bb.getASuccessor*(), v) and
|
||||
i = -1
|
||||
}
|
||||
@@ -150,10 +196,8 @@ private module Cached {
|
||||
* that writes captured variable `v`.
|
||||
*/
|
||||
cached
|
||||
predicate capturedCallWrite(
|
||||
CfgNodes::ExprNodes::CallCfgNode call, BasicBlock bb, int i, LocalVariable v
|
||||
) {
|
||||
exists(CfgScope scope |
|
||||
predicate capturedCallWrite(CallCfgNode call, CFG::BasicBlock bb, int i, LocalVariable v) {
|
||||
exists(CFG::CfgScope scope |
|
||||
hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and
|
||||
call = bb.getNode(i)
|
||||
|
|
||||
@@ -170,7 +214,9 @@ private module Cached {
|
||||
* AST write access is `write`.
|
||||
*/
|
||||
cached
|
||||
predicate variableWriteActual(BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write) {
|
||||
predicate variableWriteActual(
|
||||
CFG::BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write
|
||||
) {
|
||||
exists(AstNode n |
|
||||
write.getVariable() = v and
|
||||
n = bb.getNode(i).getNode()
|
||||
@@ -184,7 +230,7 @@ private module Cached {
|
||||
|
||||
cached
|
||||
VariableReadAccessCfgNode getARead(Definition def) {
|
||||
exists(LocalVariable v, BasicBlock bb, int i |
|
||||
exists(LocalVariable v, CFG::BasicBlock bb, int i |
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
variableReadActual(bb, i, v) and
|
||||
result = bb.getNode(i)
|
||||
@@ -193,9 +239,9 @@ private module Cached {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate defReachesCallReadInOuterScope(
|
||||
Definition def, CfgNodes::ExprNodes::CallCfgNode call, LocalVariable v, CfgScope scope
|
||||
Definition def, CallCfgNode call, LocalVariable v, CFG::CfgScope scope
|
||||
) {
|
||||
exists(BasicBlock bb, int i |
|
||||
exists(CFG::BasicBlock bb, int i |
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedCallRead(call, bb, i, v) and
|
||||
scope.getOuterCfgScope() = bb.getScope()
|
||||
@@ -203,8 +249,8 @@ private module Cached {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, CfgScope scope) {
|
||||
exists(BasicBlock bb, int i |
|
||||
private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, CFG::CfgScope scope) {
|
||||
exists(CFG::BasicBlock bb, int i |
|
||||
capturedEntryWrite(bb, i, v) and
|
||||
entry.definesAt(v, bb, i) and
|
||||
bb.getScope().getOuterCfgScope*() = scope
|
||||
@@ -221,8 +267,8 @@ private module Cached {
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
predicate captureFlowIn(CfgNodes::ExprNodes::CallCfgNode call, Definition def, Definition entry) {
|
||||
exists(LocalVariable v, CfgScope scope |
|
||||
predicate captureFlowIn(CallCfgNode call, Definition def, Definition entry) {
|
||||
exists(LocalVariable v, CFG::CfgScope scope |
|
||||
defReachesCallReadInOuterScope(def, call, v, scope) and
|
||||
hasCapturedEntryWrite(entry, v, scope)
|
||||
|
|
||||
@@ -237,8 +283,10 @@ private module Cached {
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
pragma[noinline]
|
||||
private predicate defReachesExitReadInInnerScope(Definition def, LocalVariable v, CfgScope scope) {
|
||||
exists(BasicBlock bb, int i |
|
||||
private predicate defReachesExitReadInInnerScope(
|
||||
Definition def, LocalVariable v, CFG::CfgScope scope
|
||||
) {
|
||||
exists(CFG::BasicBlock bb, int i |
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedExitRead(bb, i, v) and
|
||||
scope = bb.getScope().getOuterCfgScope*()
|
||||
@@ -247,9 +295,9 @@ private module Cached {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedExitRead(
|
||||
Definition exit, CfgNodes::ExprNodes::CallCfgNode call, LocalVariable v, CfgScope scope
|
||||
Definition exit, CallCfgNode call, LocalVariable v, CFG::CfgScope scope
|
||||
) {
|
||||
exists(BasicBlock bb, int i |
|
||||
exists(CFG::BasicBlock bb, int i |
|
||||
capturedCallWrite(call, bb, i, v) and
|
||||
exit.definesAt(v, bb, i) and
|
||||
bb.getScope() = scope.getOuterCfgScope()
|
||||
@@ -267,8 +315,8 @@ private module Cached {
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
predicate captureFlowOut(CfgNodes::ExprNodes::CallCfgNode call, Definition def, Definition exit) {
|
||||
exists(LocalVariable v, CfgScope scope |
|
||||
predicate captureFlowOut(CallCfgNode call, Definition def, Definition exit) {
|
||||
exists(LocalVariable v, CFG::CfgScope scope |
|
||||
defReachesExitReadInInnerScope(def, v, scope) and
|
||||
hasCapturedExitRead(exit, call, v, _)
|
||||
|
|
||||
@@ -281,7 +329,7 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
Definition phiHasInputFromBlock(PhiNode phi, BasicBlock bb) {
|
||||
Definition phiHasInputFromBlock(PhiNode phi, CFG::BasicBlock bb) {
|
||||
phiHasInputFromBlock(phi, result, bb)
|
||||
}
|
||||
|
||||
@@ -291,7 +339,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate firstRead(Definition def, VariableReadAccessCfgNode read) {
|
||||
exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
|
||||
exists(CFG::BasicBlock bb1, int i1, CFG::BasicBlock bb2, int i2 |
|
||||
def.definesAt(_, bb1, i1) and
|
||||
adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and
|
||||
read = bb2.getNode(i2)
|
||||
@@ -307,7 +355,7 @@ private module Cached {
|
||||
predicate adjacentReadPair(
|
||||
Definition def, VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2
|
||||
) {
|
||||
exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
|
||||
exists(CFG::BasicBlock bb1, int i1, CFG::BasicBlock bb2, int i2 |
|
||||
read1 = bb1.getNode(i1) and
|
||||
variableReadActual(bb1, i1, _) and
|
||||
adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and
|
||||
@@ -322,7 +370,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate lastRead(Definition def, VariableReadAccessCfgNode read) {
|
||||
exists(BasicBlock bb, int i |
|
||||
exists(CFG::BasicBlock bb, int i |
|
||||
lastRefNoUncertainReads(def, bb, i) and
|
||||
variableReadActual(bb, i, _) and
|
||||
read = bb.getNode(i)
|
||||
@@ -337,7 +385,7 @@ private module Cached {
|
||||
* The reference is either a read of `def` or `def` itself.
|
||||
*/
|
||||
cached
|
||||
predicate lastRefBeforeRedef(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
predicate lastRefBeforeRedef(Definition def, CFG::BasicBlock bb, int i, Definition next) {
|
||||
lastRefRedefNoUncertainReads(def, bb, i, next)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,795 +0,0 @@
|
||||
/**
|
||||
* Provides a language-independent implementation of static single assignment
|
||||
* (SSA) form.
|
||||
*/
|
||||
|
||||
private import SsaImplSpecific
|
||||
|
||||
private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb }
|
||||
|
||||
/**
|
||||
* Liveness analysis (based on source variables) to restrict the size of the
|
||||
* SSA representation.
|
||||
*/
|
||||
private module Liveness {
|
||||
/**
|
||||
* A classification of variable references into reads (of a given kind) and
|
||||
* (certain or uncertain) writes.
|
||||
*/
|
||||
private newtype TRefKind =
|
||||
Read(boolean certain) { certain in [false, true] } or
|
||||
Write(boolean certain) { certain in [false, true] }
|
||||
|
||||
private class RefKind extends TRefKind {
|
||||
string toString() {
|
||||
exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")")
|
||||
or
|
||||
exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
|
||||
}
|
||||
|
||||
int getOrder() {
|
||||
this = Read(_) and
|
||||
result = 0
|
||||
or
|
||||
this = Write(_) and
|
||||
result = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
|
||||
*/
|
||||
predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) {
|
||||
exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain))
|
||||
or
|
||||
exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
|
||||
}
|
||||
|
||||
private newtype OrderedRefIndex =
|
||||
MkOrderedRefIndex(int i, int tag) {
|
||||
exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder())
|
||||
}
|
||||
|
||||
private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) {
|
||||
ref(bb, i, v, k) and
|
||||
result = MkOrderedRefIndex(i, ord) and
|
||||
ord = k.getOrder()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (1-based) rank of the reference to `v` at the `i`th node of
|
||||
* basic block `bb`, which has the given reference kind `k`.
|
||||
*
|
||||
* Reads are considered before writes when they happen at the same index.
|
||||
*/
|
||||
private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) {
|
||||
refOrd(bb, i, v, k, _) =
|
||||
rank[result](int j, int ord, OrderedRefIndex res |
|
||||
res = refOrd(bb, j, v, _, ord)
|
||||
|
|
||||
res order by j, ord
|
||||
)
|
||||
}
|
||||
|
||||
private int maxRefRank(BasicBlock bb, SourceVariable v) {
|
||||
result = refRank(bb, _, v, _) and
|
||||
not result + 1 = refRank(bb, _, v, _)
|
||||
}
|
||||
|
||||
predicate lastRefIsRead(BasicBlock bb, SourceVariable v) {
|
||||
maxRefRank(bb, v) = refRank(bb, _, v, Read(_))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (1-based) rank of the first reference to `v` inside basic block `bb`
|
||||
* that is either a read or a certain write.
|
||||
*/
|
||||
private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) {
|
||||
result =
|
||||
min(int r, RefKind k |
|
||||
r = refRank(bb, _, v, k) and
|
||||
k != Write(false)
|
||||
|
|
||||
r
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if source variable `v` is live at the beginning of basic block `bb`.
|
||||
*/
|
||||
predicate liveAtEntry(BasicBlock bb, SourceVariable v) {
|
||||
// The first read or certain write to `v` inside `bb` is a read
|
||||
refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v)
|
||||
or
|
||||
// There is no certain write to `v` inside `bb`, but `v` is live at entry
|
||||
// to a successor basic block of `bb`
|
||||
not exists(firstReadOrCertainWrite(bb, v)) and
|
||||
liveAtExit(bb, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if source variable `v` is live at the end of basic block `bb`.
|
||||
*/
|
||||
predicate liveAtExit(BasicBlock bb, SourceVariable v) {
|
||||
liveAtEntry(getABasicBlockSuccessor(bb), v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if variable `v` is live in basic block `bb` at index `i`.
|
||||
* The rank of `i` is `rnk` as defined by `refRank()`.
|
||||
*/
|
||||
private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) {
|
||||
exists(RefKind kind | rnk = refRank(bb, i, v, kind) |
|
||||
rnk = maxRefRank(bb, v) and
|
||||
liveAtExit(bb, v)
|
||||
or
|
||||
ref(bb, i, v, kind) and
|
||||
kind = Read(_)
|
||||
or
|
||||
exists(RefKind nextKind |
|
||||
liveAtRank(bb, _, v, rnk + 1) and
|
||||
rnk + 1 = refRank(bb, _, v, nextKind) and
|
||||
nextKind != Write(true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if variable `v` is live after the (certain or uncertain) write at
|
||||
* index `i` inside basic block `bb`.
|
||||
*/
|
||||
predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) {
|
||||
exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk))
|
||||
}
|
||||
}
|
||||
|
||||
private import Liveness
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
|
||||
or
|
||||
exists(BasicBlock prev | inDominanceFrontier(prev, df) |
|
||||
bb = getImmediateBasicBlockDominator(prev) and
|
||||
not bb = getImmediateBasicBlockDominator(df)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bb` is in the dominance frontier of a block containing a
|
||||
* definition of `v`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) {
|
||||
exists(BasicBlock defbb, Definition def |
|
||||
def.definesAt(v, defbb, _) and
|
||||
inDominanceFrontier(defbb, bb)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TDefinition =
|
||||
TWriteDef(SourceVariable v, BasicBlock bb, int i) {
|
||||
variableWrite(bb, i, v, _) and
|
||||
liveAfterWrite(bb, i, v)
|
||||
} or
|
||||
TPhiNode(SourceVariable v, BasicBlock bb) {
|
||||
inDefDominanceFrontier(bb, v) and
|
||||
liveAtEntry(bb, v)
|
||||
}
|
||||
|
||||
private module SsaDefReaches {
|
||||
newtype TSsaRefKind =
|
||||
SsaActualRead() or
|
||||
SsaPhiRead() or
|
||||
SsaDef()
|
||||
|
||||
class SsaRead = SsaActualRead or SsaPhiRead;
|
||||
|
||||
/**
|
||||
* A classification of SSA variable references into reads and definitions.
|
||||
*/
|
||||
class SsaRefKind extends TSsaRefKind {
|
||||
string toString() {
|
||||
this = SsaActualRead() and
|
||||
result = "SsaActualRead"
|
||||
or
|
||||
this = SsaPhiRead() and
|
||||
result = "SsaPhiRead"
|
||||
or
|
||||
this = SsaDef() and
|
||||
result = "SsaDef"
|
||||
}
|
||||
|
||||
int getOrder() {
|
||||
this instanceof SsaRead and
|
||||
result = 0
|
||||
or
|
||||
this = SsaDef() and
|
||||
result = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bb` is in the dominance frontier of a block containing a
|
||||
* read of `v`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) {
|
||||
exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) |
|
||||
lastRefIsRead(readbb, v)
|
||||
or
|
||||
phiRead(readbb, v)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a phi-read node should be inserted for variable `v` at the beginning
|
||||
* of basic block `bb`.
|
||||
*
|
||||
* Phi-read nodes are like normal phi nodes, but they are inserted based on reads
|
||||
* instead of writes, and only if the dominance-frontier block does not already
|
||||
* contain a reference (read or write) to `v`. Unlike normal phi nodes, this is
|
||||
* an internal implementation detail that is not exposed.
|
||||
*
|
||||
* The motivation for adding phi-reads is to improve performance of the use-use
|
||||
* calculation in cases where there is a large number of reads that can reach the
|
||||
* same join-point, and from there reach a large number of basic blocks. Example:
|
||||
*
|
||||
* ```cs
|
||||
* if (a)
|
||||
* use(x);
|
||||
* else if (b)
|
||||
* use(x);
|
||||
* else if (c)
|
||||
* use(x);
|
||||
* else if (d)
|
||||
* use(x);
|
||||
* // many more ifs ...
|
||||
*
|
||||
* // phi-read for `x` inserted here
|
||||
*
|
||||
* // program not mentioning `x`, with large basic block graph
|
||||
*
|
||||
* use(x);
|
||||
* ```
|
||||
*
|
||||
* Without phi-reads, the analysis has to replicate reachability for each of
|
||||
* the guarded uses of `x`. However, with phi-reads, the analysis will limit
|
||||
* each conditional use of `x` to reach the basic block containing the phi-read
|
||||
* node for `x`, and only that basic block will have to compute reachability
|
||||
* through the remainder of the large program.
|
||||
*
|
||||
* Like normal reads, each phi-read node `phi-read` can be reached from exactly
|
||||
* one SSA definition (without passing through another definition): Assume, for
|
||||
* the sake of contradiction, that there are two reaching definitions `def1` and
|
||||
* `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest
|
||||
* dominating definition will prevent the other from reaching `phi-read`. So, at
|
||||
* least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`.
|
||||
* Then `def1` must go through one of its dominance-frontier blocks in order to
|
||||
* reach `phi-read`. However, such a block will always start with a (normal) phi
|
||||
* node, which contradicts reachability.
|
||||
*
|
||||
* Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`,
|
||||
* will dominate `phi-read`. Assuming it doesn't means that the path from `def`
|
||||
* to `phi-read` goes through a dominance-frontier block, and hence a phi node,
|
||||
* which contradicts reachability.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate phiRead(BasicBlock bb, SourceVariable v) {
|
||||
inReadDominanceFrontier(bb, v) and
|
||||
liveAtEntry(bb, v) and
|
||||
// only if there are no other references to `v` inside `bb`
|
||||
not ref(bb, _, v, _) and
|
||||
not exists(Definition def | def.definesAt(v, bb, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th node of basic block `bb` is a reference to `v`,
|
||||
* either a read (when `k` is `SsaRead()`) or an SSA definition (when `k`
|
||||
* is `SsaDef()`).
|
||||
*
|
||||
* Unlike `Liveness::ref`, this includes `phi` nodes.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
|
||||
variableRead(bb, i, v, _) and
|
||||
k = SsaActualRead()
|
||||
or
|
||||
phiRead(bb, v) and
|
||||
i = -1 and
|
||||
k = SsaPhiRead()
|
||||
or
|
||||
any(Definition def).definesAt(v, bb, i) and
|
||||
k = SsaDef()
|
||||
}
|
||||
|
||||
private newtype OrderedSsaRefIndex =
|
||||
MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) }
|
||||
|
||||
private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) {
|
||||
ssaRef(bb, i, v, k) and
|
||||
result = MkOrderedSsaRefIndex(i, k) and
|
||||
ord = k.getOrder()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (1-based) rank of the reference to `v` at the `i`th node of basic
|
||||
* block `bb`, which has the given reference kind `k`.
|
||||
*
|
||||
* For example, if `bb` is a basic block with a phi node for `v` (considered
|
||||
* to be at index -1), reads `v` at node 2, and defines it at node 5, we have:
|
||||
*
|
||||
* ```ql
|
||||
* ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node
|
||||
* ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2
|
||||
* ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5
|
||||
* ```
|
||||
*
|
||||
* Reads are considered before writes when they happen at the same index.
|
||||
*/
|
||||
int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
|
||||
ssaRefOrd(bb, i, v, k, _) =
|
||||
rank[result](int j, int ord, OrderedSsaRefIndex res |
|
||||
res = ssaRefOrd(bb, j, v, _, ord)
|
||||
|
|
||||
res order by j, ord
|
||||
)
|
||||
}
|
||||
|
||||
int maxSsaRefRank(BasicBlock bb, SourceVariable v) {
|
||||
result = ssaRefRank(bb, _, v, _) and
|
||||
not result + 1 = ssaRefRank(bb, _, v, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the SSA definition `def` reaches rank index `rnk` in its own
|
||||
* basic block `bb`.
|
||||
*/
|
||||
predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) {
|
||||
exists(int i |
|
||||
rnk = ssaRefRank(bb, i, v, SsaDef()) and
|
||||
def.definesAt(v, bb, i)
|
||||
)
|
||||
or
|
||||
ssaDefReachesRank(bb, def, rnk - 1, v) and
|
||||
rnk = ssaRefRank(bb, _, v, any(SsaRead k))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the SSA definition of `v` at `def` reaches index `i` in the same
|
||||
* basic block `bb`, without crossing another SSA definition of `v`.
|
||||
*/
|
||||
predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) {
|
||||
exists(int rnk |
|
||||
ssaDefReachesRank(bb, def, rnk, v) and
|
||||
rnk = ssaRefRank(bb, i, v, any(SsaRead k))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`.
|
||||
*/
|
||||
int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) {
|
||||
v = def.getSourceVariable() and
|
||||
result = ssaRefRank(bb, i, v, k) and
|
||||
(
|
||||
ssaDefReachesRead(_, def, bb, i)
|
||||
or
|
||||
def.definesAt(_, bb, i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the reference to `def` at index `i` in basic block `bb` is the
|
||||
* last reference to `v` inside `bb`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
|
||||
}
|
||||
|
||||
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) {
|
||||
exists(ssaDefRank(def, v, bb, _, k))
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) {
|
||||
ssaDefReachesEndOfBlock(bb, def, _) and
|
||||
not defOccursInBlock(_, bb, def.getSourceVariable(), _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is accessed in basic block `bb1` (either a read or a write),
|
||||
* `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_
|
||||
* predecessor of `bb2`, and the underlying variable for `def` is neither read
|
||||
* nor written in any block on the path between `bb1` and `bb2`.
|
||||
*
|
||||
* Phi reads are considered as normal reads for this predicate.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) {
|
||||
defOccursInBlock(def, bb1, _, _) and
|
||||
bb2 = getABasicBlockSuccessor(bb1)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
varBlockReachesInclPhiRead(def, bb1, mid) and
|
||||
ssaDefReachesThroughBlock(def, mid) and
|
||||
bb2 = getABasicBlockSuccessor(mid)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) {
|
||||
varBlockReachesInclPhiRead(def, bb1, bb2) and
|
||||
defOccursInBlock(def, bb2, v, SsaPhiRead())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) {
|
||||
varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and
|
||||
ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()])
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
varBlockReachesExclPhiRead(def, mid, bb2) and
|
||||
phiReadStep(def, _, bb1, mid)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is accessed in basic block `bb1` (either a read or a write),
|
||||
* the underlying variable `v` of `def` is accessed in basic block `bb2`
|
||||
* (either a read or a write), `bb2` is a transitive successor of `bb1`, and
|
||||
* `v` is neither read nor written in any block on the path between `bb1`
|
||||
* and `bb2`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) {
|
||||
varBlockReachesExclPhiRead(def, bb1, bb2) and
|
||||
not defOccursInBlock(def, bb1, _, SsaPhiRead())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
|
||||
varBlockReaches(def, bb1, bb2) and
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is accessed in basic block `bb` (either a read or a write),
|
||||
* `bb1` can reach a transitive successor `bb2` where `def` is no longer live,
|
||||
* and `v` is neither read nor written in any block on the path between `bb`
|
||||
* and `bb2`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate varBlockReachesExit(Definition def, BasicBlock bb) {
|
||||
exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) |
|
||||
not defOccursInBlock(def, bb2, _, _) and
|
||||
not ssaDefReachesEndOfBlock(bb2, def, _)
|
||||
)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
varBlockReachesExit(def, mid) and
|
||||
phiReadStep(def, _, bb, mid)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
predicate phiReadExposedForTesting = phiRead/2;
|
||||
|
||||
private import SsaDefReaches
|
||||
|
||||
pragma[nomagic]
|
||||
predicate liveThrough(BasicBlock bb, SourceVariable v) {
|
||||
liveAtExit(bb, v) and
|
||||
not ssaRef(bb, _, v, SsaDef())
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the SSA definition of `v` at `def` reaches the end of basic
|
||||
* block `bb`, at which point it is still live, without crossing another
|
||||
* SSA definition of `v`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
|
||||
exists(int last |
|
||||
last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and
|
||||
ssaDefReachesRank(bb, def, last, v) and
|
||||
liveAtExit(bb, v)
|
||||
)
|
||||
or
|
||||
// The construction of SSA form ensures that each read of a variable is
|
||||
// dominated by its definition. An SSA definition therefore reaches a
|
||||
// control flow node if it is the _closest_ SSA definition that dominates
|
||||
// the node. If two definitions dominate a node then one must dominate the
|
||||
// other, so therefore the definition of _closest_ is given by the dominator
|
||||
// tree. Thus, reaching definitions can be calculated in terms of dominance.
|
||||
ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
|
||||
liveThrough(bb, pragma[only_bind_into](v))
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) {
|
||||
exists(SourceVariable v, BasicBlock bbDef |
|
||||
phi.definesAt(v, bbDef, _) and
|
||||
getABasicBlockPredecessor(bbDef) = bb and
|
||||
ssaDefReachesEndOfBlock(bb, inp, v)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the SSA definition of `v` at `def` reaches a read at index `i` in
|
||||
* basic block `bb`, without crossing another SSA definition of `v`. The read
|
||||
* is of kind `rk`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
|
||||
ssaDefReachesReadWithinBlock(v, def, bb, i)
|
||||
or
|
||||
ssaRef(bb, i, v, any(SsaRead k)) and
|
||||
ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and
|
||||
not ssaDefReachesReadWithinBlock(v, _, bb, i)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read
|
||||
* or a write), `def` is read at index `i2` in basic block `bb2`, and there is a
|
||||
* path between them without any read of `def`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
|
||||
exists(int rnk |
|
||||
rnk = ssaDefRank(def, _, bb1, i1, _) and
|
||||
rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and
|
||||
variableRead(bb1, i2, _, _) and
|
||||
bb2 = bb1
|
||||
)
|
||||
or
|
||||
lastSsaRef(def, _, bb1, i1) and
|
||||
defAdjacentRead(def, bb1, bb2, i2)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate adjacentDefRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
|
||||
ssaRef(bb1, i1, v, SsaDef())
|
||||
or
|
||||
variableRead(bb1, i1, v, true)
|
||||
)
|
||||
or
|
||||
exists(BasicBlock bb3, int i3 |
|
||||
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
|
||||
variableRead(bb3, i3, _, false) and
|
||||
adjacentDefRead(def, bb3, i3, bb2, i2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `adjacentDefRead`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
|
||||
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
|
||||
variableRead(bb2, i2, _, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the node at index `i` in `bb` is a last reference to SSA definition
|
||||
* `def`. The reference is last because it can reach another write `next`,
|
||||
* without passing through another read or write.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
exists(SourceVariable v |
|
||||
// Next reference to `v` inside `bb` is a write
|
||||
exists(int rnk, int j |
|
||||
rnk = ssaDefRank(def, v, bb, i, _) and
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
)
|
||||
or
|
||||
// Can reach a write using one or more steps
|
||||
lastSsaRef(def, v, bb, i) and
|
||||
exists(BasicBlock bb2 |
|
||||
varBlockReaches(def, bb, bb2) and
|
||||
1 = ssaDefRank(next, v, bb2, _, SsaDef())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if `inp` is an immediately preceding definition of uncertain definition
|
||||
* `def`. Since `def` is uncertain, the value from the preceding definition might
|
||||
* still be valid.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
|
||||
lastRefRedef(inp, _, _, def)
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesUncertainRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
|
||||
variableRead(bb2, i2, _, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `lastRefRedef`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
lastRefRedef(def, bb, i, next) and
|
||||
not variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(BasicBlock bb0, int i0 |
|
||||
lastRefRedef(def, bb0, i0, next) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the node at index `i` in `bb` is a last reference to SSA
|
||||
* definition `def`.
|
||||
*
|
||||
* That is, the node can reach the end of the enclosing callable, or another
|
||||
* SSA definition for the underlying source variable, without passing through
|
||||
* another read.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRef(Definition def, BasicBlock bb, int i) {
|
||||
// Can reach another definition
|
||||
lastRefRedef(def, bb, i, _)
|
||||
or
|
||||
exists(SourceVariable v | lastSsaRef(def, v, bb, i) |
|
||||
// Can reach exit directly
|
||||
bb instanceof ExitBasicBlock
|
||||
or
|
||||
// Can reach a block using one or more steps, where `def` is no longer live
|
||||
varBlockReachesExit(def, bb)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `lastRefRedef`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) {
|
||||
lastRef(def, bb, i) and
|
||||
not variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(BasicBlock bb0, int i0 |
|
||||
lastRef(def, bb0, i0) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
/** A static single assignment (SSA) definition. */
|
||||
class Definition extends TDefinition {
|
||||
/** Gets the source variable underlying this SSA definition. */
|
||||
SourceVariable getSourceVariable() { this.definesAt(result, _, _) }
|
||||
|
||||
/**
|
||||
* Holds if this SSA definition defines `v` at index `i` in basic block `bb`.
|
||||
* Phi nodes are considered to be at index `-1`, while normal variable writes
|
||||
* are at the index of the control flow node they wrap.
|
||||
*/
|
||||
final predicate definesAt(SourceVariable v, BasicBlock bb, int i) {
|
||||
this = TWriteDef(v, bb, i)
|
||||
or
|
||||
this = TPhiNode(v, bb) and i = -1
|
||||
}
|
||||
|
||||
/** Gets the basic block to which this SSA definition belongs. */
|
||||
final BasicBlock getBasicBlock() { this.definesAt(_, result, _) }
|
||||
|
||||
/** Gets a textual representation of this SSA definition. */
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/** An SSA definition that corresponds to a write. */
|
||||
class WriteDefinition extends Definition, TWriteDef {
|
||||
private SourceVariable v;
|
||||
private BasicBlock bb;
|
||||
private int i;
|
||||
|
||||
WriteDefinition() { this = TWriteDef(v, bb, i) }
|
||||
|
||||
override string toString() { result = "WriteDef" }
|
||||
}
|
||||
|
||||
/** A phi node. */
|
||||
class PhiNode extends Definition, TPhiNode {
|
||||
override string toString() { result = "Phi" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An SSA definition that represents an uncertain update of the underlying
|
||||
* source variable.
|
||||
*/
|
||||
class UncertainWriteDefinition extends WriteDefinition {
|
||||
UncertainWriteDefinition() {
|
||||
exists(SourceVariable v, BasicBlock bb, int i |
|
||||
this.definesAt(v, bb, i) and
|
||||
variableWrite(bb, i, v, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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(v, def0, bb, i)))
|
||||
}
|
||||
|
||||
query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) {
|
||||
variableRead(bb, i, v, _) and
|
||||
not ssaDefReachesRead(v, _, bb, i)
|
||||
}
|
||||
|
||||
query predicate deadDef(RelevantDefinition def, SourceVariable v) {
|
||||
v = def.getSourceVariable() and
|
||||
not ssaDefReachesRead(_, def, _, _) and
|
||||
not phiHasInputFromBlock(_, def, _) and
|
||||
not uncertainWriteDefinitionInput(_, def)
|
||||
}
|
||||
|
||||
query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) |
|
||||
ssaDefReachesReadWithinBlock(v, def, bb, i) and
|
||||
(bb != bbDef or i < iDef)
|
||||
or
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
not ssaDefReachesReadWithinBlock(v, def, bb, i) and
|
||||
not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/** Provides the Ruby specific parameters for `SsaImplCommon.qll`. */
|
||||
|
||||
private import SsaImpl as SsaImpl
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
|
||||
class BasicBlock = BasicBlocks::BasicBlock;
|
||||
|
||||
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() }
|
||||
|
||||
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
|
||||
|
||||
class ExitBasicBlock = BasicBlocks::ExitBasicBlock;
|
||||
|
||||
class SourceVariable = LocalVariable;
|
||||
|
||||
/**
|
||||
* Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`.
|
||||
* `certain` is true if the write definitely occurs.
|
||||
*/
|
||||
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
|
||||
(
|
||||
exists(Scope scope | scope = v.(SelfVariable).getDeclaringScope() |
|
||||
// We consider the `self` variable to have a single write at the entry to a method block...
|
||||
scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and
|
||||
i = 0
|
||||
or
|
||||
// ...or a class or module block.
|
||||
bb.getNode(i).getNode() = scope.(ModuleBase).getAControlFlowEntryNode()
|
||||
)
|
||||
or
|
||||
SsaImpl::uninitializedWrite(bb, i, v)
|
||||
or
|
||||
SsaImpl::capturedEntryWrite(bb, i, v)
|
||||
or
|
||||
SsaImpl::variableWriteActual(bb, i, v, _)
|
||||
) and
|
||||
certain = true
|
||||
or
|
||||
SsaImpl::capturedCallWrite(_, bb, i, v) and
|
||||
certain = false
|
||||
}
|
||||
|
||||
predicate variableRead = SsaImpl::variableRead/4;
|
||||
@@ -172,7 +172,12 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class ActionControllerControllerClass extends ClassDeclaration {
|
||||
* A public instance method defined within an `ActionController` controller class.
|
||||
* This may be the target of a route handler, if such a route is defined.
|
||||
*/
|
||||
class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
class ActionControllerActionMethod extends Method, Http::Server::RequestHandler::Range {
|
||||
private ActionControllerControllerClass controllerClass;
|
||||
|
||||
ActionControllerActionMethod() { this = controllerClass.getAMethod() and not this.isPrivate() }
|
||||
@@ -126,7 +126,7 @@ abstract class ParamsCall extends MethodCall {
|
||||
* A `RemoteFlowSource::Range` to represent accessing the
|
||||
* ActionController parameters available via the `params` method.
|
||||
*/
|
||||
class ParamsSource extends HTTP::Server::RequestInputAccess::Range {
|
||||
class ParamsSource extends Http::Server::RequestInputAccess::Range {
|
||||
ParamsSource() { this.asExpr().getExpr() instanceof ParamsCall }
|
||||
|
||||
override string getSourceType() { result = "ActionController::Metal#params" }
|
||||
@@ -143,7 +143,7 @@ abstract class CookiesCall extends MethodCall {
|
||||
* A `RemoteFlowSource::Range` to represent accessing the
|
||||
* ActionController parameters available via the `cookies` method.
|
||||
*/
|
||||
class CookiesSource extends HTTP::Server::RequestInputAccess::Range {
|
||||
class CookiesSource extends Http::Server::RequestInputAccess::Range {
|
||||
CookiesSource() { this.asExpr().getExpr() instanceof CookiesCall }
|
||||
|
||||
override string getSourceType() { result = "ActionController::Metal#cookies" }
|
||||
@@ -211,7 +211,7 @@ class RedirectToCall extends ActionControllerContextCall {
|
||||
/**
|
||||
* A call to the `redirect_to` method, as an `HttpRedirectResponse`.
|
||||
*/
|
||||
class ActionControllerRedirectResponse extends HTTP::Server::HttpRedirectResponse::Range {
|
||||
class ActionControllerRedirectResponse extends Http::Server::HttpRedirectResponse::Range {
|
||||
RedirectToCall redirectToCall;
|
||||
|
||||
ActionControllerRedirectResponse() { this.asExpr().getExpr() = redirectToCall }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.DataFlow
|
||||
@@ -127,7 +128,7 @@ abstract class RenderCall extends MethodCall {
|
||||
* A call to `render`, `render_to_body` or `render_to_string`, seen as an
|
||||
* `HttpResponse`.
|
||||
*/
|
||||
private class RenderCallAsHttpResponse extends DataFlow::CallNode, HTTP::Server::HttpResponse::Range {
|
||||
private class RenderCallAsHttpResponse extends DataFlow::CallNode, Http::Server::HttpResponse::Range {
|
||||
RenderCallAsHttpResponse() {
|
||||
this.asExpr().getExpr() instanceof RenderCall or
|
||||
this.asExpr().getExpr() instanceof RenderToCall
|
||||
@@ -204,4 +205,15 @@ class LinkToCall extends ActionViewContextCall {
|
||||
not exists(this.getBlock()) and result = this.getArgument(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An instantiation of `ActionView::FileSystemResolver`, considered as a `FileSystemAccess`.
|
||||
*/
|
||||
class FileSystemResolverAccess extends DataFlow::CallNode, FileSystemAccess::Range {
|
||||
FileSystemResolverAccess() {
|
||||
this = API::getTopLevelMember("ActionView").getMember("FileSystemResolver").getAnInstantiation()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
// TODO: model flow in/out of template files properly,
|
||||
|
||||
298
ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll
Normal file
298
ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Provides modeling for the `ActiveResource` library.
|
||||
* Version: 6.0.0.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides modeling for the `ActiveResource` library.
|
||||
* Version: 6.0.0.
|
||||
*/
|
||||
module ActiveResource {
|
||||
/**
|
||||
* An ActiveResource model class. This is any (transitive) subclass of ActiveResource.
|
||||
*/
|
||||
private API::Node modelApiNode() {
|
||||
result = API::getTopLevelMember("ActiveResource").getMember("Base").getASubclass+()
|
||||
}
|
||||
|
||||
/**
|
||||
* An ActiveResource class.
|
||||
*
|
||||
* ```rb
|
||||
* class Person < ActiveResource::Base
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class ModelClass extends ClassDeclaration {
|
||||
API::Node model;
|
||||
|
||||
ModelClass() {
|
||||
model = modelApiNode() and
|
||||
this.getSuperclassExpr() = model.getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
|
||||
/** Gets the API node for this model */
|
||||
API::Node getModelApiNode() { result = model }
|
||||
|
||||
/** Gets a call to `site=`, which sets the base URL for this model. */
|
||||
SiteAssignCall getASiteAssignment() { result.getModelClass() = this }
|
||||
|
||||
/** Holds if `c` sets a base URL which does not use HTTPS. */
|
||||
predicate disablesCertificateValidation(SiteAssignCall c) {
|
||||
c = this.getASiteAssignment() and
|
||||
c.disablesCertificateValidation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a class method on an ActiveResource model class.
|
||||
*
|
||||
* ```rb
|
||||
* class Person < ActiveResource::Base
|
||||
* end
|
||||
*
|
||||
* Person.find(1)
|
||||
* ```
|
||||
*/
|
||||
class ModelClassMethodCall extends DataFlow::CallNode {
|
||||
API::Node model;
|
||||
|
||||
ModelClassMethodCall() {
|
||||
model = modelApiNode() and
|
||||
this = classMethodCall(model, _)
|
||||
}
|
||||
|
||||
/** Gets the model class for this call. */
|
||||
ModelClass getModelClass() { result.getModelApiNode() = model }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `site=` on an ActiveResource model class.
|
||||
* This sets the base URL for all HTTP requests made by this class.
|
||||
*/
|
||||
private class SiteAssignCall extends DataFlow::CallNode {
|
||||
API::Node model;
|
||||
|
||||
SiteAssignCall() { model = modelApiNode() and this = classMethodCall(model, "site=") }
|
||||
|
||||
/**
|
||||
* Gets a node that contributes to the URLs used for HTTP requests by the parent
|
||||
* class.
|
||||
*/
|
||||
DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
/** Gets the model class for this call. */
|
||||
ModelClass getModelClass() { result.getModelApiNode() = model }
|
||||
|
||||
/** Holds if this site value specifies HTTP rather than HTTPS. */
|
||||
predicate disablesCertificateValidation() {
|
||||
this.getAUrlPart()
|
||||
.asExpr()
|
||||
.(ExprNodes::AssignExprCfgNode)
|
||||
.getRhs()
|
||||
.getConstantValue()
|
||||
.getString()
|
||||
.regexpMatch("^http[^s].+")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `find` class method, which returns an ActiveResource model
|
||||
* object.
|
||||
*
|
||||
* ```rb
|
||||
* alice = Person.find(1)
|
||||
* ```
|
||||
*/
|
||||
class FindCall extends ModelClassMethodCall {
|
||||
FindCall() { this.getMethodName() = "find" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `create(!)` class method, which returns an ActiveResource model
|
||||
* object.
|
||||
*
|
||||
* ```rb
|
||||
* alice = Person.create(name: "alice")
|
||||
* ```
|
||||
*/
|
||||
class CreateCall extends ModelClassMethodCall {
|
||||
CreateCall() { this.getMethodName() = ["create", "create!"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method that sends a custom HTTP request outside of the
|
||||
* ActiveResource conventions. This typically returns an ActiveResource model
|
||||
* object. It may return a collection, but we don't currently model that.
|
||||
*
|
||||
* ```rb
|
||||
* alice = Person.get(:active)
|
||||
* ```
|
||||
*/
|
||||
class CustomHttpCall extends ModelClassMethodCall {
|
||||
CustomHttpCall() { this.getMethodName() = ["get", "post", "put", "patch", "delete"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* An ActiveResource model object.
|
||||
*/
|
||||
class ModelInstance extends DataFlow::Node {
|
||||
ModelClass cls;
|
||||
|
||||
ModelInstance() {
|
||||
exists(API::Node model | model = modelApiNode() |
|
||||
this = model.getInstance().getAValueReachableFromSource() and
|
||||
cls.getModelApiNode() = model
|
||||
)
|
||||
or
|
||||
exists(FindCall call | call.flowsTo(this) | cls = call.getModelClass())
|
||||
or
|
||||
exists(CreateCall call | call.flowsTo(this) | cls = call.getModelClass())
|
||||
or
|
||||
exists(CustomHttpCall call | call.flowsTo(this) | cls = call.getModelClass())
|
||||
or
|
||||
exists(CollectionCall call |
|
||||
call.getMethodName() = ["first", "last"] and
|
||||
call.flowsTo(this)
|
||||
|
|
||||
cls = call.getCollection().getModelClass()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the model class for this instance. */
|
||||
ModelClass getModelClass() { result = cls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on an ActiveResource model object.
|
||||
*/
|
||||
class ModelInstanceMethodCall extends DataFlow::CallNode {
|
||||
ModelInstance i;
|
||||
|
||||
ModelInstanceMethodCall() { this.getReceiver() = i }
|
||||
|
||||
/** Gets the model instance for this call. */
|
||||
ModelInstance getInstance() { result = i }
|
||||
|
||||
/** Gets the model class for this call. */
|
||||
ModelClass getModelClass() { result = i.getModelClass() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of ActiveResource model objects.
|
||||
*/
|
||||
class Collection extends DataFlow::Node {
|
||||
ModelClassMethodCall classMethodCall;
|
||||
|
||||
Collection() {
|
||||
classMethodCall.flowsTo(this) and
|
||||
(
|
||||
classMethodCall.getMethodName() = "all"
|
||||
or
|
||||
classMethodCall.getMethodName() = "find" and
|
||||
classMethodCall.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all")
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the model class for this collection. */
|
||||
ModelClass getModelClass() { result = classMethodCall.getModelClass() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on a collection.
|
||||
*/
|
||||
class CollectionCall extends DataFlow::CallNode {
|
||||
CollectionCall() { this.getReceiver() instanceof Collection }
|
||||
|
||||
/** Gets the collection for this call. */
|
||||
Collection getCollection() { result = this.getReceiver() }
|
||||
}
|
||||
|
||||
private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range,
|
||||
ModelClassMethodCall {
|
||||
ModelClass cls;
|
||||
|
||||
ModelClassMethodCallAsHttpRequest() {
|
||||
this.getModelClass() = cls and
|
||||
this.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"]
|
||||
}
|
||||
|
||||
override string getFramework() { result = "ActiveResource" }
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
cls.disablesCertificateValidation(disablingNode) and
|
||||
// TODO: highlight real argument origin
|
||||
argumentOrigin = disablingNode
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = this }
|
||||
}
|
||||
|
||||
private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range,
|
||||
ModelInstanceMethodCall {
|
||||
ModelClass cls;
|
||||
|
||||
ModelInstanceMethodCallAsHttpRequest() {
|
||||
this.getModelClass() = cls and
|
||||
this.getMethodName() =
|
||||
[
|
||||
"exists?", "reload", "save", "save!", "destroy", "delete", "get", "patch", "post", "put",
|
||||
"update_attribute", "update_attributes"
|
||||
]
|
||||
}
|
||||
|
||||
override string getFramework() { result = "ActiveResource" }
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
cls.disablesCertificateValidation(disablingNode) and
|
||||
// TODO: highlight real argument origin
|
||||
argumentOrigin = disablingNode
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a class method.
|
||||
*
|
||||
* TODO: is this general enough to be useful elsewhere?
|
||||
*
|
||||
* Examples:
|
||||
* ```rb
|
||||
* class A
|
||||
* def self.m; end
|
||||
*
|
||||
* m # call
|
||||
* end
|
||||
*
|
||||
* A.m # call
|
||||
* ```
|
||||
*/
|
||||
private DataFlow::CallNode classMethodCall(API::Node classNode, string methodName) {
|
||||
// A.m
|
||||
result = classNode.getAMethodCall(methodName)
|
||||
or
|
||||
// class A
|
||||
// A.m
|
||||
// end
|
||||
result.getReceiver().asExpr() instanceof ExprNodes::SelfVariableAccessCfgNode and
|
||||
result.asExpr().getExpr().getEnclosingModule().(ClassDeclaration).getSuperclassExpr() =
|
||||
classNode.getAValueReachableFromSource().asExpr().getExpr() and
|
||||
result.getMethodName() = methodName
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ private class GraphqlResolvableClass extends ClassDeclaration {
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlResolveMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
class GraphqlResolveMethod extends Method, Http::Server::RequestHandler::Range {
|
||||
private GraphqlResolvableClass resolvableClass;
|
||||
|
||||
GraphqlResolveMethod() { this = resolvableClass.getMethod("resolve") }
|
||||
@@ -208,7 +208,7 @@ class GraphqlResolveMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlLoadMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
class GraphqlLoadMethod extends Method, Http::Server::RequestHandler::Range {
|
||||
private GraphqlResolvableClass resolvableClass;
|
||||
|
||||
GraphqlLoadMethod() {
|
||||
@@ -340,7 +340,7 @@ private class GraphqlFieldArgumentDefinitionMethodCall extends GraphqlSchemaObje
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlFieldResolutionMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler::Range {
|
||||
private GraphqlSchemaObjectClass schemaObjectClass;
|
||||
|
||||
GraphqlFieldResolutionMethod() {
|
||||
|
||||
@@ -97,13 +97,13 @@ class Feature extends TFeature {
|
||||
abstract string getConstantName();
|
||||
}
|
||||
|
||||
private class FeatureNOENT extends Feature, TNOENT {
|
||||
private class FeatureNoent extends Feature, TNOENT {
|
||||
override int getValue() { result = 2 }
|
||||
|
||||
override string getConstantName() { result = "NOENT" }
|
||||
}
|
||||
|
||||
private class FeatureNONET extends Feature, TNONET {
|
||||
private class FeatureNonet extends Feature, TNONET {
|
||||
override int getValue() { result = 2048 }
|
||||
|
||||
override string getConstantName() { result = "NONET" }
|
||||
|
||||
@@ -72,6 +72,8 @@ private class Unit = Specific::Unit;
|
||||
|
||||
private module API = Specific::API;
|
||||
|
||||
private module DataFlow = Specific::DataFlow;
|
||||
|
||||
private import Specific::AccessPathSyntax
|
||||
|
||||
/** Module containing hooks for providing input data to be interpreted as a model. */
|
||||
@@ -155,6 +157,38 @@ module ModelInput {
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type model rows from CodeQL models.
|
||||
*/
|
||||
class TypeModel extends Unit {
|
||||
/**
|
||||
* Gets a data-flow node that is a source of the type `package;type`.
|
||||
*/
|
||||
DataFlow::Node getASource(string package, string type) { none() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node that is a sink of the type `package;type`,
|
||||
* usually because it is an argument passed to a parameter of that type.
|
||||
*/
|
||||
DataFlow::Node getASink(string package, string type) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type variable model rows.
|
||||
*/
|
||||
class TypeVariableModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a path through a type variable.
|
||||
*
|
||||
* A row of form,
|
||||
* ```
|
||||
* name;path
|
||||
* ```
|
||||
* means `path` can be substituted for a token `TypeVar[name]`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
}
|
||||
|
||||
private import ModelInput
|
||||
@@ -182,6 +216,8 @@ private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inverseP
|
||||
|
||||
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
|
||||
|
||||
/** Holds if a source model exists for the given parameters. */
|
||||
predicate sourceModel(string package, string type, string path, string kind) {
|
||||
exists(string row |
|
||||
@@ -219,7 +255,7 @@ private predicate summaryModel(
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if an type model exists for the given parameters. */
|
||||
/** Holds if a type model exists for the given parameters. */
|
||||
private predicate typeModel(
|
||||
string package1, string type1, string package2, string type2, string path
|
||||
) {
|
||||
@@ -233,6 +269,15 @@ private predicate typeModel(
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a type variable model exists for the given parameters. */
|
||||
private predicate typeVariableModel(string name, string path) {
|
||||
exists(string row |
|
||||
typeVariableModel(row) and
|
||||
row.splitAt(";", 0) = name and
|
||||
row.splitAt(";", 1) = path
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a package that should be seen as an alias for the given other `package`,
|
||||
* or the `package` itself.
|
||||
@@ -253,7 +298,7 @@ private predicate isRelevantPackage(string package) {
|
||||
sourceModel(package, _, _, _) or
|
||||
sinkModel(package, _, _, _) or
|
||||
summaryModel(package, _, _, _, _, _) or
|
||||
typeModel(package, _, _, _, _)
|
||||
typeModel(_, _, package, _, _)
|
||||
) and
|
||||
(
|
||||
Specific::isPackageUsed(package)
|
||||
@@ -290,6 +335,8 @@ private class AccessPathRange extends AccessPath::Range {
|
||||
summaryModel(package, _, _, this, _, _) or
|
||||
summaryModel(package, _, _, _, this, _)
|
||||
)
|
||||
or
|
||||
typeVariableModel(_, this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +386,57 @@ private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, A
|
||||
Specific::invocationMatchesExtraCallSiteFilter(invoke, token)
|
||||
}
|
||||
|
||||
private class TypeModelUseEntry extends API::EntryPoint {
|
||||
private string package;
|
||||
private string type;
|
||||
|
||||
TypeModelUseEntry() {
|
||||
exists(any(TypeModel tm).getASource(package, type)) and
|
||||
this = "TypeModelUseEntry;" + package + ";" + type
|
||||
}
|
||||
|
||||
override DataFlow::LocalSourceNode getASource() {
|
||||
result = any(TypeModel tm).getASource(package, type)
|
||||
}
|
||||
|
||||
API::Node getNodeForType(string package_, string type_) {
|
||||
package = package_ and type = type_ and result = this.getANode()
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeModelDefEntry extends API::EntryPoint {
|
||||
private string package;
|
||||
private string type;
|
||||
|
||||
TypeModelDefEntry() {
|
||||
exists(any(TypeModel tm).getASink(package, type)) and
|
||||
this = "TypeModelDefEntry;" + package + ";" + type
|
||||
}
|
||||
|
||||
override DataFlow::Node getASink() { result = any(TypeModel tm).getASink(package, type) }
|
||||
|
||||
API::Node getNodeForType(string package_, string type_) {
|
||||
package = package_ and type = type_ and result = this.getANode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node identified by the given `(package,type)` pair.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromType(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2)
|
||||
)
|
||||
or
|
||||
result = any(TypeModelUseEntry e).getNodeForType(package, type)
|
||||
or
|
||||
result = any(TypeModelDefEntry e).getNodeForType(package, type)
|
||||
or
|
||||
result = Specific::getExtraNodeFromType(package, type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
|
||||
*/
|
||||
@@ -347,12 +445,8 @@ private API::Node getNodeFromPath(string package, string type, AccessPath path,
|
||||
isRelevantFullPath(package, type, path) and
|
||||
(
|
||||
n = 0 and
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2, path2.getNumToken())
|
||||
)
|
||||
result = getNodeFromType(package, type)
|
||||
or
|
||||
// Language-specific cases, such as handling of global variables
|
||||
result = Specific::getExtraNodeFromPath(package, type, path, n)
|
||||
)
|
||||
or
|
||||
@@ -361,6 +455,72 @@ private API::Node getNodeFromPath(string package, string type, AccessPath path,
|
||||
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
or
|
||||
// Apply a subpath
|
||||
result =
|
||||
getNodeFromSubPath(getNodeFromPath(package, type, path, n - 1), getSubPathAt(path, n - 1))
|
||||
or
|
||||
// Apply a type step
|
||||
typeStep(getNodeFromPath(package, type, path, n), result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a subpath for the `TypeVar` token found at the `n`th token of `path`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private AccessPath getSubPathAt(AccessPath path, int n) {
|
||||
exists(string typeVarName |
|
||||
path.getToken(n).getAnArgument("TypeVar") = typeVarName and
|
||||
typeVariableModel(typeVarName, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is found by evaluating the first `n` tokens of `subPath` starting at `base`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath, int n) {
|
||||
exists(AccessPath path, int k |
|
||||
base = [getNodeFromPath(_, _, path, k), getNodeFromSubPath(_, path, k)] and
|
||||
subPath = getSubPathAt(path, k) and
|
||||
result = base and
|
||||
n = 0
|
||||
)
|
||||
or
|
||||
exists(string package, string type, AccessPath basePath |
|
||||
typeStepModel(package, type, basePath, subPath) and
|
||||
base = getNodeFromPath(package, type, basePath) and
|
||||
result = base and
|
||||
n = 0
|
||||
)
|
||||
or
|
||||
result = getSuccessorFromNode(getNodeFromSubPath(base, subPath, n - 1), subPath.getToken(n - 1))
|
||||
or
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromSubPath(base, subPath, n - 1), subPath.getToken(n - 1))
|
||||
or
|
||||
result =
|
||||
getNodeFromSubPath(getNodeFromSubPath(base, subPath, n - 1), getSubPathAt(subPath, n - 1))
|
||||
or
|
||||
typeStep(getNodeFromSubPath(base, subPath, n), result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call site that is found by evaluating the first `n` tokens of `subPath` starting at `base`.
|
||||
*/
|
||||
private Specific::InvokeNode getInvocationFromSubPath(API::Node base, AccessPath subPath, int n) {
|
||||
result = Specific::getAnInvocationOf(getNodeFromSubPath(base, subPath, n))
|
||||
or
|
||||
result = getInvocationFromSubPath(base, subPath, n - 1) and
|
||||
invocationMatchesCallSiteFilter(result, subPath.getToken(n - 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is found by evaluating `subPath` starting at `base`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath) {
|
||||
result = getNodeFromSubPath(base, subPath, subPath.getNumToken())
|
||||
}
|
||||
|
||||
/** Gets the node identified by the given `(package, type, path)` tuple. */
|
||||
@@ -368,6 +528,20 @@ API::Node getNodeFromPath(string package, string type, AccessPath path) {
|
||||
result = getNodeFromPath(package, type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStepModel(string package, string type, AccessPath basePath, AccessPath output) {
|
||||
summaryModel(package, type, basePath, "", output, "type")
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStep(API::Node pred, API::Node succ) {
|
||||
exists(string package, string type, AccessPath basePath, AccessPath output |
|
||||
typeStepModel(package, type, basePath, output) and
|
||||
pred = getNodeFromPath(package, type, basePath) and
|
||||
succ = getNodeFromSubPath(pred, output)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invocation identified by the given `(package, type, path)` tuple.
|
||||
*
|
||||
@@ -390,7 +564,7 @@ Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPa
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity"]
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity", "TypeVar"]
|
||||
or
|
||||
Specific::isExtraValidTokenNameInIdentifyingAccessPath(name)
|
||||
}
|
||||
@@ -418,6 +592,9 @@ predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argume
|
||||
name = "WithArity" and
|
||||
argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?")
|
||||
or
|
||||
name = "TypeVar" and
|
||||
exists(argument)
|
||||
or
|
||||
Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument)
|
||||
}
|
||||
|
||||
@@ -469,12 +646,7 @@ module ModelOutput {
|
||||
* Holds if `node` is seen as an instance of `(package,type)` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
*/
|
||||
API::Node getATypeNode(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path |
|
||||
typeModel(package, type, package2, type2, path) and
|
||||
result = getNodeFromPath(package2, type2, path)
|
||||
)
|
||||
}
|
||||
API::Node getATypeNode(string package, string type) { result = getNodeFromType(package, type) }
|
||||
|
||||
/**
|
||||
* Gets an error message relating to an invalid CSV row in a model.
|
||||
@@ -489,6 +661,8 @@ module ModelOutput {
|
||||
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6
|
||||
or
|
||||
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5
|
||||
or
|
||||
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
|
||||
|
|
||||
actualArity = count(row.indexOf(";")) + 1 and
|
||||
actualArity != expectedArity and
|
||||
@@ -499,7 +673,7 @@ module ModelOutput {
|
||||
or
|
||||
// Check names and arguments of access path tokens
|
||||
exists(AccessPath path, AccessPathToken token |
|
||||
isRelevantFullPath(_, _, path) and
|
||||
(isRelevantFullPath(_, _, path) or typeVariableModel(_, path)) and
|
||||
token = path.getToken(_)
|
||||
|
|
||||
not isValidTokenNameInIdentifyingAccessPath(token.getName()) and
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import ApiGraphModels
|
||||
|
||||
@@ -29,6 +28,7 @@ class Unit = DataFlowPrivate::Unit;
|
||||
// Re-export libraries needed by ApiGraphModels.qll
|
||||
import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.dataflow.internal.AccessPathSyntax as AccessPathSyntax
|
||||
import codeql.ruby.DataFlow::DataFlow as DataFlow
|
||||
private import AccessPathSyntax
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||
@@ -55,12 +55,6 @@ predicate isPackageUsed(string package) {
|
||||
/** Gets a Ruby-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
|
||||
bindingset[package, type, path]
|
||||
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(package, type, path) and
|
||||
exists(package) and // Allow any package name, see `isPackageUsed`.
|
||||
type = "" and
|
||||
n = 0 and
|
||||
result = API::root()
|
||||
or
|
||||
// A row of form `;any;Method[foo]` should match any method named `foo`.
|
||||
exists(package) and
|
||||
type = "any" and
|
||||
@@ -71,6 +65,13 @@ API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a Ruby-specific interpretation of the `(package, type)` tuple. */
|
||||
API::Node getExtraNodeFromType(string package, string type) {
|
||||
isRelevantFullPath(package, type, _) and // Allow any package name, see `isPackageUsed`.
|
||||
type = "" and
|
||||
result = API::root()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `path` occurs in a CSV row with type `any`, meaning it can start
|
||||
* matching anywhere, and the path begins with `Method[methodName]`.
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Excon`.
|
||||
@@ -23,14 +24,13 @@ private import codeql.ruby.DataFlow
|
||||
* TODO: pipelining, streaming responses
|
||||
* https://github.com/excon/excon/blob/master/README.md
|
||||
*/
|
||||
class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::CallNode requestUse;
|
||||
class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
DataFlow::Node connectionUse;
|
||||
|
||||
ExconHttpRequest() {
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestNode.asSource() and
|
||||
connectionUse = connectionNode.asSource() and
|
||||
connectionNode =
|
||||
[
|
||||
@@ -46,8 +46,7 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
// Excon#request exists but Excon.request doesn't.
|
||||
// This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
|
||||
"get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
|
||||
]) and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
])
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
@@ -56,96 +55,76 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
// 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
|
||||
result = this.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
|
||||
or
|
||||
result = requestUse.getKeywordArgument("path")
|
||||
result = this.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 |
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
argSetsVerifyPeer(arg, false, disablingNode)
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
|
||||
// Check for `ssl_verify_peer: false`
|
||||
result = newCall.getKeywordArgumentIncludeHashArgument("ssl_verify_peer")
|
||||
)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(ExconDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
or
|
||||
// Or we see a call to `Excon.defaults[:ssl_verify_peer] = false` before the
|
||||
// request, and no `ssl_verify_peer: true` in the explicit options hash for
|
||||
// the request call.
|
||||
exists(DataFlow::CallNode disableCall |
|
||||
setsDefaultVerification(disableCall, false) and
|
||||
disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
|
||||
disablingNode = disableCall and
|
||||
not exists(DataFlow::Node arg, int i |
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
argSetsVerifyPeer(arg, true, _)
|
||||
)
|
||||
// We set `Excon.defaults[:ssl_verify_peer]` or `Excon.ssl_verify_peer` = false`
|
||||
// before the request, and no `ssl_verify_peer: true` in the explicit options hash
|
||||
// for the request call.
|
||||
exists(DataFlow::CallNode disableCall, BooleanLiteral value |
|
||||
// Excon.defaults[:ssl_verify_peer]
|
||||
disableCall = API::getTopLevelMember("Excon").getReturn("defaults").getAMethodCall("[]=") and
|
||||
disableCall
|
||||
.getArgument(0)
|
||||
.getALocalSource()
|
||||
.asExpr()
|
||||
.getConstantValue()
|
||||
.isStringlikeValue("ssl_verify_peer") and
|
||||
disablingNode = disableCall.getArgument(1) and
|
||||
argumentOrigin = disablingNode.getALocalSource() and
|
||||
value = argumentOrigin.asExpr().getExpr()
|
||||
or
|
||||
// Excon.ssl_verify_peer
|
||||
disableCall = API::getTopLevelMember("Excon").getAMethodCall("ssl_verify_peer=") and
|
||||
disablingNode = disableCall.getArgument(0) and
|
||||
argumentOrigin = disablingNode.getALocalSource() and
|
||||
value = argumentOrigin.asExpr().getExpr()
|
||||
|
|
||||
value.getValue() = false and
|
||||
disableCall.asExpr().getASuccessor+() = this.asExpr() and
|
||||
// no `ssl_verify_peer: true` in the request call.
|
||||
not this.getCertificateValidationControllingValue()
|
||||
.getALocalSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
.(BooleanLiteral)
|
||||
.getValue() = true
|
||||
)
|
||||
}
|
||||
|
||||
override string getFramework() { result = "Excon" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` represents an options hash that contains the key
|
||||
* `:ssl_verify_peer` with `value`, where `kvNode` is the data-flow node for
|
||||
* this key-value pair.
|
||||
*/
|
||||
predicate argSetsVerifyPeer(DataFlow::Node arg, boolean value, DataFlow::Node kvNode) {
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// Excon.get(..., ssl_verify_peer: false)
|
||||
isSslVerifyPeerPair(arg.asExpr(), value) and
|
||||
kvNode = arg
|
||||
or
|
||||
// Or as a single hash argument, e.g.:
|
||||
// Excon.get(..., { ssl_verify_peer: false, ... })
|
||||
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
isSslVerifyPeerPair(p, value) and
|
||||
optionsNode.flowsTo(arg) and
|
||||
kvNode.asExpr() = p
|
||||
)
|
||||
}
|
||||
/** A configuration to track values that can disable certificate validation for Excon. */
|
||||
private class ExconDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
ExconDisablesCertificateValidationConfiguration() {
|
||||
this = "ExconDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `callNode` sets `Excon.defaults[:ssl_verify_peer]` or
|
||||
* `Excon.ssl_verify_peer` to `value`.
|
||||
*/
|
||||
private predicate setsDefaultVerification(DataFlow::CallNode callNode, boolean value) {
|
||||
callNode = API::getTopLevelMember("Excon").getReturn("defaults").getAMethodCall("[]=") and
|
||||
isSslVerifyPeerLiteral(callNode.getArgument(0)) and
|
||||
hasBooleanValue(callNode.getArgument(1), value)
|
||||
or
|
||||
callNode = API::getTopLevelMember("Excon").getAMethodCall("ssl_verify_peer=") and
|
||||
hasBooleanValue(callNode.getArgument(0), value)
|
||||
}
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse()
|
||||
}
|
||||
|
||||
private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("ssl_verify_peer") and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `node` can contain `value`. */
|
||||
private predicate hasBooleanValue(DataFlow::Node node, boolean value) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().(BooleanLiteral).getValue() = value and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `p` is the pair `ssl_verify_peer: <value>`. */
|
||||
private predicate isSslVerifyPeerPair(CfgNodes::ExprNodes::PairCfgNode p, boolean value) {
|
||||
exists(DataFlow::Node key, DataFlow::Node valueNode |
|
||||
key.asExpr() = p.getKey() and
|
||||
valueNode.asExpr() = p.getValue() and
|
||||
isSslVerifyPeerLiteral(key) and
|
||||
hasBooleanValue(valueNode, value)
|
||||
)
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(ExconHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Faraday`.
|
||||
@@ -22,11 +23,10 @@ private import codeql.ruby.DataFlow
|
||||
* connection.get("/").body
|
||||
* ```
|
||||
*/
|
||||
class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
DataFlow::Node connectionUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
FaradayHttpRequest() {
|
||||
connectionNode =
|
||||
@@ -38,125 +38,67 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
] and
|
||||
requestNode =
|
||||
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
|
||||
requestUse = requestNode.asSource() and
|
||||
connectionUse = connectionNode.asSource() and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
this = requestNode.asSource() and
|
||||
connectionUse = connectionNode.asSource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
override DataFlow::Node getAUrlPart() {
|
||||
result = requestUse.getArgument(0) or
|
||||
result = this.getArgument(0) or
|
||||
result = connectionUse.(DataFlow::CallNode).getArgument(0) or
|
||||
result = connectionUse.(DataFlow::CallNode).getKeywordArgument("url")
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
/** Gets the value that controls certificate validation, if any, with argument name `name`. */
|
||||
DataFlow::Node getCertificateValidationControllingValue(string argName) {
|
||||
// `Faraday::new` takes an options hash as its second argument, and we're
|
||||
// looking for
|
||||
// `{ ssl: { verify: false } }`
|
||||
// or
|
||||
// `{ ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }`
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
argName in ["verify", "verify_mode"] and
|
||||
exists(DataFlow::Node sslValue, DataFlow::CallNode newCall |
|
||||
newCall = connectionNode.getAValueReachableFromSource() and
|
||||
sslValue = newCall.getKeywordArgumentIncludeHashArgument("ssl")
|
||||
|
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// Faraday.new(..., ssl: {...})
|
||||
isSslOptionsPairDisablingValidation(arg.asExpr()) and
|
||||
disablingNode = arg
|
||||
or
|
||||
// Or as a single hash argument, e.g.:
|
||||
// Faraday.new(..., { ssl: {...} })
|
||||
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
isSslOptionsPairDisablingValidation(p) and
|
||||
optionsNode.flowsTo(arg) and
|
||||
disablingNode.asExpr() = p
|
||||
exists(CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key |
|
||||
p = sslValue.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
key.asExpr() = p.getKey() and
|
||||
key.getALocalSource().asExpr().getConstantValue().isStringlikeValue(argName) and
|
||||
result.asExpr() = p.getValue()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(FaradayDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue(_)
|
||||
}
|
||||
|
||||
override string getFramework() { result = "Faraday" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the pair `p` contains the key `:ssl` for which the value is a hash
|
||||
* containing either `verify: false` or
|
||||
* `verify_mode: OpenSSL::SSL::VERIFY_NONE`.
|
||||
*/
|
||||
private predicate isSslOptionsPairDisablingValidation(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSymbolLiteral(key, "ssl") and
|
||||
(isHashWithVerifyFalse(value) or isHashWithVerifyModeNone(value))
|
||||
)
|
||||
}
|
||||
/** A configuration to track values that can disable certificate validation for Faraday. */
|
||||
private class FaradayDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
FaradayDisablesCertificateValidationConfiguration() {
|
||||
this = "FaradayDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
/** Holds if `node` represents the symbol literal with the given `valueText`. */
|
||||
private predicate isSymbolLiteral(DataFlow::Node node, string valueText) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().getConstantValue().isStringlikeValue(valueText) and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
override predicate isSource(
|
||||
DataFlow::Node source, DataFlowImplForHttpClientLibraries::FlowState state
|
||||
) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
state = "verify"
|
||||
or
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource() and
|
||||
state = "verify_mode"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` represents a hash containing the key-value pair
|
||||
* `verify: false`.
|
||||
*/
|
||||
private predicate isHashWithVerifyFalse(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode hash |
|
||||
isVerifyFalsePair(hash.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair()) and
|
||||
hash.flowsTo(node)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` represents a hash containing the key-value pair
|
||||
* `verify_mode: OpenSSL::SSL::VERIFY_NONE`.
|
||||
*/
|
||||
private predicate isHashWithVerifyModeNone(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode hash |
|
||||
isVerifyModeNonePair(hash.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair()) and
|
||||
hash.flowsTo(node)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the pair `p` has the key `:verify_mode` and the value
|
||||
* `OpenSSL::SSL::VERIFY_NONE`.
|
||||
*/
|
||||
private predicate isVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSymbolLiteral(key, "verify_mode") and
|
||||
value =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the pair `p` has the key `:verify` and the value `false`.
|
||||
*/
|
||||
private predicate isVerifyFalsePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSymbolLiteral(key, "verify") and
|
||||
isFalse(value)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `node` can contain the Boolean value `false`. */
|
||||
private predicate isFalse(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
override predicate isSink(DataFlow::Node sink, DataFlowImplForHttpClientLibraries::FlowState state) {
|
||||
sink = any(FaradayHttpRequest req).getCertificateValidationControllingValue(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `HTTPClient`.
|
||||
@@ -14,10 +15,9 @@ private import codeql.ruby.DataFlow
|
||||
* HTTPClient.get_content("http://example.com")
|
||||
* ```
|
||||
*/
|
||||
class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
DataFlow::CallNode requestUse;
|
||||
string method;
|
||||
|
||||
HttpClientRequest() {
|
||||
@@ -29,36 +29,60 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
API::getTopLevelMember("HTTPClient").getInstance()
|
||||
] and
|
||||
requestNode = connectionNode.getReturn(method) and
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestNode.asSource() and
|
||||
method in [
|
||||
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
|
||||
] and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
|
||||
override DataFlow::Node getAUrlPart() { result = this.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
|
||||
// has various methods that return the response body.
|
||||
method in ["get_content", "post_content"] and result = requestUse
|
||||
method in ["get_content", "post_content"] and result = this
|
||||
or
|
||||
not method in ["get_content", "put_content"] and
|
||||
result = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
// Look for calls to set
|
||||
// `c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE`
|
||||
// on an HTTPClient connection object `c`.
|
||||
disablingNode = connectionNode.getReturn("ssl_config").getReturn("verify_mode=").asSource() and
|
||||
disablingNode.(DataFlow::CallNode).getArgument(0) =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
result =
|
||||
connectionNode
|
||||
.getReturn("ssl_config")
|
||||
.getReturn("verify_mode=")
|
||||
.asSource()
|
||||
.(DataFlow::CallNode)
|
||||
.getArgument(0)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(HttpClientDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "HTTPClient" }
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for HttpClient. */
|
||||
private class HttpClientDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
HttpClientDisablesCertificateValidationConfiguration() {
|
||||
this = "HttpClientDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(HttpClientRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `HTTParty`.
|
||||
@@ -23,19 +24,17 @@ private import codeql.ruby.DataFlow
|
||||
* MyClass.new("http://example.com")
|
||||
* ```
|
||||
*/
|
||||
class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||
class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
HttpartyRequest() {
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestNode.asSource() and
|
||||
requestNode =
|
||||
API::getTopLevelMember("HTTParty")
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
|
||||
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() {
|
||||
// If HTTParty can recognise the response type, it will parse and return it
|
||||
@@ -46,60 +45,36 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||
or
|
||||
// Otherwise, treat the response as the response body.
|
||||
not exists(requestNode.getAMethodCall("body")) and
|
||||
result = requestUse
|
||||
result = this
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
// The various request methods take an options hash as their second
|
||||
// argument, and we're looking for `{ verify: false }` or
|
||||
// `{ verify_peer: false }`.
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and
|
||||
arg.asExpr() = requestUse.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i)
|
||||
|
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// HTTParty.get(..., verify: false)
|
||||
isVerifyFalsePair(arg.asExpr()) and
|
||||
disablingNode = arg
|
||||
or
|
||||
// Or as a single hash argument, e.g.:
|
||||
// HTTParty.get(..., { verify: false, ... })
|
||||
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
isVerifyFalsePair(p) and
|
||||
optionsNode.flowsTo(arg) and
|
||||
disablingNode.asExpr() = p
|
||||
)
|
||||
)
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
result = this.getKeywordArgumentIncludeHashArgument(["verify", "verify_peer"])
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(HttpartyDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "HTTParty" }
|
||||
}
|
||||
|
||||
/** Holds if `node` represents the symbol literal `verify` or `verify_peer`. */
|
||||
private predicate isVerifyLiteral(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().getConstantValue().isStringlikeValue(["verify", "verify_peer"]) and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
/** A configuration to track values that can disable certificate validation for Httparty. */
|
||||
private class HttpartyDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
HttpartyDisablesCertificateValidationConfiguration() {
|
||||
this = "HttpartyDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
/** Holds if `node` can contain the Boolean value `false`. */
|
||||
private predicate isFalse(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` is the pair `verify: false` or `verify_peer: false`.
|
||||
*/
|
||||
private predicate isVerifyFalsePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isVerifyLiteral(key) and
|
||||
isFalse(value)
|
||||
)
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(HttpartyRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPublic
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A `Net::HTTP` call which initiates an HTTP request.
|
||||
@@ -18,7 +19,7 @@ private import codeql.ruby.DataFlow
|
||||
* response = req.get("/")
|
||||
* ```
|
||||
*/
|
||||
class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
private DataFlow::CallNode request;
|
||||
private DataFlow::Node responseBody;
|
||||
private API::Node requestNode;
|
||||
@@ -26,7 +27,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
NetHttpRequest() {
|
||||
exists(string method |
|
||||
request = requestNode.asSource() and
|
||||
this = request.asExpr().getExpr()
|
||||
this = request
|
||||
|
|
||||
// Net::HTTP.get(...)
|
||||
method = "get" and
|
||||
@@ -65,23 +66,42 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
// A Net::HTTP request bypasses certificate validation if we see a setter
|
||||
// call like this:
|
||||
// foo.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
// and then the receiver of that call flows to the receiver in the request:
|
||||
// foo.request(...)
|
||||
exists(DataFlow::CallNode setter |
|
||||
disablingNode =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource() and
|
||||
setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and
|
||||
disablingNode = setter.getArgument(0) and
|
||||
result = setter.getArgument(0) and
|
||||
localFlow(setter.getReceiver(), request.getReceiver())
|
||||
)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(NetHttpDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "Net::HTTP" }
|
||||
}
|
||||
|
||||
/** A configuration to track values that can disable certificate validation for NetHttp. */
|
||||
private class NetHttpDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
NetHttpDisablesCertificateValidationConfiguration() {
|
||||
this = "NetHttpDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(NetHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.Core
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `OpenURI` via `URI.open` or
|
||||
@@ -18,9 +19,8 @@ private import codeql.ruby.frameworks.Core
|
||||
* URI.parse("http://example.com").open.read
|
||||
* ```
|
||||
*/
|
||||
class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
OpenUriRequest() {
|
||||
requestNode =
|
||||
@@ -28,21 +28,26 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
|
||||
.getReturn("open"), API::getTopLevelMember("OpenURI").getReturn("open_uri")
|
||||
] and
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
this = requestNode.asSource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
|
||||
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() {
|
||||
result = requestNode.getAMethodCall(["read", "readlines"])
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
exists(DataFlow::Node arg |
|
||||
arg.asExpr() = requestUse.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getAnArgument() and
|
||||
argumentDisablesValidation(arg, disablingNode)
|
||||
)
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
result = this.getKeywordArgumentIncludeHashArgument("ssl_verify_mode")
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(OpenUriDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "OpenURI" }
|
||||
@@ -56,72 +61,60 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
* Kernel.open("http://example.com").read
|
||||
* ```
|
||||
*/
|
||||
class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
OpenUriKernelOpenRequest() {
|
||||
requestUse instanceof KernelMethodCall and
|
||||
this.getMethodName() = "open" and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
this instanceof KernelMethodCall and
|
||||
this.getMethodName() = "open"
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
|
||||
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::CallNode getResponseBody() {
|
||||
result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
|
||||
requestUse.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
|
||||
this.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and
|
||||
arg.asExpr() = requestUse.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i) and
|
||||
argumentDisablesValidation(arg, disablingNode)
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
result = this.getKeywordArgument("ssl_verify_mode")
|
||||
or
|
||||
// using a hashliteral
|
||||
exists(
|
||||
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key
|
||||
|
|
||||
// can't flow to argument 0, since that's the URL
|
||||
optionsNode.flowsTo(this.getArgument(any(int i | i > 0))) and
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
key.asExpr() = p.getKey() and
|
||||
key.getALocalSource().asExpr().getConstantValue().isStringlikeValue("ssl_verify_mode") and
|
||||
result.asExpr() = p.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(OpenUriDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "OpenURI" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the argument `arg` is an options hash that disables certificate
|
||||
* validation, and `disablingNode` is the specific node representing the
|
||||
* `ssl_verify_mode: OpenSSL::SSL_VERIFY_NONE` pair.
|
||||
*/
|
||||
private predicate argumentDisablesValidation(DataFlow::Node arg, DataFlow::Node disablingNode) {
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// URI.open(..., ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
||||
isSslVerifyModeNonePair(arg.asExpr()) and
|
||||
disablingNode = arg
|
||||
or
|
||||
// Or as a single hash argument, e.g.:
|
||||
// URI.open(..., { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, ... })
|
||||
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
isSslVerifyModeNonePair(p) and
|
||||
optionsNode.flowsTo(arg) and
|
||||
disablingNode.asExpr() = p
|
||||
)
|
||||
}
|
||||
/** A configuration to track values that can disable certificate validation for OpenURI. */
|
||||
private class OpenUriDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
OpenUriDisablesCertificateValidationConfiguration() {
|
||||
this = "OpenUriDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
/** Holds if `p` is the pair `ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE`. */
|
||||
private predicate isSslVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSslVerifyModeLiteral(key) and
|
||||
value =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
/** Holds if `node` can represent the symbol literal `:ssl_verify_mode`. */
|
||||
private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("ssl_verify_mode") and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(OpenUriRequest req).getCertificateValidationControllingValue()
|
||||
or
|
||||
sink = any(OpenUriKernelOpenRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `RestClient`.
|
||||
@@ -16,14 +17,12 @@ private import codeql.ruby.DataFlow
|
||||
* RestClient::Request.execute(url: "http://example.com").body
|
||||
* ```
|
||||
*/
|
||||
class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::CallNode requestUse;
|
||||
class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
|
||||
RestClientHttpRequest() {
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestUse.asExpr().getExpr() and
|
||||
this = requestNode.asSource() and
|
||||
(
|
||||
connectionNode =
|
||||
[
|
||||
@@ -39,59 +38,44 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() {
|
||||
result = requestUse.getKeywordArgument("url")
|
||||
result = this.getKeywordArgument("url")
|
||||
or
|
||||
result = requestUse.getArgument(0) and
|
||||
result = this.getArgument(0) and
|
||||
// this rules out the alternative above
|
||||
not result.asExpr().getExpr() instanceof Pair
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
// `RestClient::Resource::new` takes an options hash argument, and we're
|
||||
// looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE)
|
||||
isVerifySslNonePair(arg.asExpr()) and
|
||||
disablingNode = arg
|
||||
or
|
||||
// Or as a single hash argument, e.g.:
|
||||
// RestClient::Resource.new(..., { verify_ssl: OpenSSL::SSL::VERIFY_NONE })
|
||||
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
isVerifySslNonePair(p) and
|
||||
optionsNode.flowsTo(arg) and
|
||||
disablingNode.asExpr() = p
|
||||
)
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
|
||||
result = newCall.getKeywordArgumentIncludeHashArgument("verify_ssl")
|
||||
)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(RestClientDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "RestClient" }
|
||||
}
|
||||
|
||||
/** Holds if `p` is the pair `verify_ssl: OpenSSL::SSL::VERIFY_NONE`. */
|
||||
private predicate isVerifySslNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSslVerifyModeLiteral(key) and
|
||||
value =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
/** A configuration to track values that can disable certificate validation for RestClient. */
|
||||
private class RestClientDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
RestClientDisablesCertificateValidationConfiguration() {
|
||||
this = "RestClientDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
/** Holds if `node` can represent the symbol literal `:verify_ssl`. */
|
||||
private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("verify_ssl") and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(RestClientHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForHttpClientLibraries as DataFlowImplForHttpClientLibraries
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Typhoeus`.
|
||||
@@ -14,68 +15,47 @@ private import codeql.ruby.DataFlow
|
||||
* Typhoeus.get("http://example.com").body
|
||||
* ```
|
||||
*/
|
||||
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::CallNode requestUse;
|
||||
class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
API::Node requestNode;
|
||||
|
||||
TyphoeusHttpRequest() {
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestNode.asSource() and
|
||||
requestNode =
|
||||
API::getTopLevelMember("Typhoeus")
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
|
||||
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
// Check for `ssl_verifypeer: false` in the options hash.
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
|
||||
|
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// Typhoeus.get(..., ssl_verifypeer: false)
|
||||
isSslVerifyPeerFalsePair(arg.asExpr()) and
|
||||
disablingNode = arg
|
||||
or
|
||||
// Or as a single hash argument, e.g.:
|
||||
// Typhoeus.get(..., { ssl_verifypeer: false, ... })
|
||||
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
|
||||
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
|
||||
isSslVerifyPeerFalsePair(p) and
|
||||
optionsNode.flowsTo(arg) and
|
||||
disablingNode.asExpr() = p
|
||||
)
|
||||
)
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
result = this.getKeywordArgumentIncludeHashArgument("ssl_verifypeer")
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
any(TyphoeusDisablesCertificateValidationConfiguration config)
|
||||
.hasFlow(argumentOrigin, disablingNode) and
|
||||
disablingNode = this.getCertificateValidationControllingValue()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "Typhoeus" }
|
||||
}
|
||||
|
||||
/** Holds if `p` is the pair `ssl_verifypeer: false`. */
|
||||
private predicate isSslVerifyPeerFalsePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
exists(DataFlow::Node key, DataFlow::Node value |
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSslVerifyPeerLiteral(key) and
|
||||
isFalse(value)
|
||||
)
|
||||
}
|
||||
/** A configuration to track values that can disable certificate validation for Typhoeus. */
|
||||
private class TyphoeusDisablesCertificateValidationConfiguration extends DataFlowImplForHttpClientLibraries::Configuration {
|
||||
TyphoeusDisablesCertificateValidationConfiguration() {
|
||||
this = "TyphoeusDisablesCertificateValidationConfiguration"
|
||||
}
|
||||
|
||||
/** Holds if `node` represents the symbol literal `verify` or `verify_peer`. */
|
||||
private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("ssl_verifypeer") and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().getExpr().(BooleanLiteral).isFalse()
|
||||
}
|
||||
|
||||
/** Holds if `node` can contain the Boolean value `false`. */
|
||||
private predicate isFalse(DataFlow::Node node) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(TyphoeusHttpRequest req).getCertificateValidationControllingValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,3 +87,70 @@ module Cryptography {
|
||||
predicate isWeak() { this = "ECB" }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP-related APIs. */
|
||||
module Http {
|
||||
/** Provides classes for modeling HTTP clients. */
|
||||
module Client {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Http::Client::Request::Range` instead.
|
||||
*/
|
||||
class Request extends DataFlow::Node instanceof Request::Range {
|
||||
/**
|
||||
* Gets a data-flow 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 getAUrlPart() { result = super.getAUrlPart() }
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
string getFramework() { result = super.getFramework() }
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled, and `argumentOrigin` represents the origin
|
||||
* of the argument that disabled the validation (which could be the same node as
|
||||
* `disablingNode`).
|
||||
*/
|
||||
predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
super.disablesCertificateValidation(disablingNode, argumentOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new HTTP requests. */
|
||||
module Request {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `Http::Client::Request` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a data-flow 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 getAUrlPart();
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
abstract string getFramework();
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled, and `argumentOrigin` represents the origin
|
||||
* of the argument that disabled the validation (which could be the same node as
|
||||
* `disablingNode`).
|
||||
*/
|
||||
abstract predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about hard-coded
|
||||
* data being interpreted as code, as well as extension points for adding your
|
||||
* own.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.security.CodeInjectionCustomizations
|
||||
private import codeql.ruby.AST as AST
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about hard-coded
|
||||
* data being interpreted as code, as well as extension points for adding your
|
||||
* own.
|
||||
*/
|
||||
module HardcodedDataInterpretedAsCode {
|
||||
/**
|
||||
* Flow states used to distinguish value-preserving flow from taint flow.
|
||||
*/
|
||||
module FlowState {
|
||||
/** Flow state used to track value-preserving flow. */
|
||||
DataFlow::FlowState data() { result = "data" }
|
||||
|
||||
/** Flow state used to tainted data (non-value preserving flow). */
|
||||
DataFlow::FlowState taint() { result = "taint" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source for hard-coded data.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/** Gets a flow label for which this is a source. */
|
||||
DataFlow::FlowState getLabel() { result = FlowState::data() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for code injection.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/** Gets a description of what kind of sink this is. */
|
||||
abstract string getKind();
|
||||
|
||||
/** Gets a flow label for which this is a sink. */
|
||||
DataFlow::FlowState getLabel() {
|
||||
// We want to ignore value-flow and only consider taint-flow, since the
|
||||
// source is just a hex string, and evaluating that directly will just
|
||||
// cause a syntax error.
|
||||
result = FlowState::taint()
|
||||
}
|
||||
}
|
||||
|
||||
/** A sanitizer for hard-coded data. */
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A constant string consisting of eight or more hexadecimal characters (including at
|
||||
* least one digit), viewed as a source of hard-coded data that should not be
|
||||
* interpreted as code.
|
||||
*/
|
||||
private class HexStringSource extends Source {
|
||||
HexStringSource() {
|
||||
exists(string val |
|
||||
this.asExpr().(ExprNodes::StringLiteralCfgNode).getConstantValue().isString(val)
|
||||
|
|
||||
val.regexpMatch("[0-9a-fA-F]{8,}") and
|
||||
val.regexpMatch(".*[0-9].*")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A string literal whose raw text is made up entirely of `\x` escape
|
||||
* sequences, viewed as a source of hard-coded data that should not be
|
||||
* interpreted as code.
|
||||
*/
|
||||
private class HexEscapedStringSource extends Source {
|
||||
HexEscapedStringSource() {
|
||||
forex(StringComponentCfgNode c |
|
||||
c = this.asExpr().(ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
|
|
||||
c.getNode().(AST::StringEscapeSequenceComponent).getRawText().matches("\\x%")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A code injection sink; hard-coded data should not flow here.
|
||||
*/
|
||||
private class DefaultCodeInjectionSink extends Sink {
|
||||
DefaultCodeInjectionSink() { this instanceof CodeInjection::Sink }
|
||||
|
||||
override string getKind() { result = "code" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to `require` path; hard-coded data should not flow here.
|
||||
*/
|
||||
private class RequireArgumentSink extends Sink {
|
||||
RequireArgumentSink() {
|
||||
exists(DataFlow::CallNode require |
|
||||
require.getReceiver().getExprNode().getExpr() instanceof AST::SelfVariableAccess and
|
||||
require.getMethodName() = "require"
|
||||
|
|
||||
this = require.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
override string getKind() { result = "an import path" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about hard-coded data
|
||||
* being interpreted as code.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `HardcodedDataInterpretedAsCode::Configuration` is needed, otherwise
|
||||
* `HardcodedDataInterpretedAsCodeCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.dataflow.internal.TaintTrackingPrivate
|
||||
import HardcodedDataInterpretedAsCodeCustomizations::HardcodedDataInterpretedAsCode
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about hard-coded data
|
||||
* being interpreted as code.
|
||||
*
|
||||
* We extend `DataFlow::Configuration` rather than
|
||||
* `TaintTracking::Configuration`, so that we can set the flow state to
|
||||
* `"taint"` on a taint step.
|
||||
*/
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "HardcodedDataInterpretedAsCode" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState label) {
|
||||
source.(Source).getLabel() = label
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState label) {
|
||||
sink.(Sink).getLabel() = label
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
super.isBarrier(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
|
||||
DataFlow::FlowState stateTo
|
||||
) {
|
||||
defaultAdditionalTaintStep(nodeFrom, nodeTo) and
|
||||
// This is a taint step, so the flow state becomes `taint`.
|
||||
stateFrom = [FlowState::data(), FlowState::taint()] and
|
||||
stateTo = FlowState::taint()
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ private import HttpToFileAccessCustomizations::HttpToFileAccess
|
||||
/**
|
||||
* An access to a user-controlled HTTP request input, considered as a flow source for writing user-controlled data to files
|
||||
*/
|
||||
private class RequestInputAccessAsSource extends Source instanceof HTTP::Server::RequestInputAccess {
|
||||
private class RequestInputAccessAsSource extends Source instanceof Http::Server::RequestInputAccess {
|
||||
}
|
||||
|
||||
/** A response from an outgoing HTTP request, considered as a flow source for writing user-controlled data to files. */
|
||||
private class HttpResponseAsSource extends Source {
|
||||
HttpResponseAsSource() { this = any(HTTP::Client::Request r).getResponseBody() }
|
||||
HttpResponseAsSource() { this = any(Http::Client::Request r).getResponseBody() }
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ module InsecureDownload {
|
||||
* In other words, if the URL is HTTP and the extension is in `unsafeExtension()`.
|
||||
*/
|
||||
private class HttpResponseAsSink extends Sink {
|
||||
private HTTP::Client::Request req;
|
||||
private Http::Client::Request req;
|
||||
|
||||
HttpResponseAsSink() {
|
||||
this = req.getAUrlPart() and
|
||||
@@ -143,7 +143,7 @@ module InsecureDownload {
|
||||
hasUnsafeExtension(req.getAUrlPart().asExpr().getConstantValue().getString())
|
||||
}
|
||||
|
||||
override DataFlow::Node getDownloadCall() { result.asExpr().getExpr() = req }
|
||||
override DataFlow::Node getDownloadCall() { result = req }
|
||||
|
||||
override DataFlow::FlowState getALabel() {
|
||||
result instanceof Label::SensitiveInsecure
|
||||
@@ -155,7 +155,7 @@ module InsecureDownload {
|
||||
/**
|
||||
* Gets a node for the response from `request`, type-tracked using `t`.
|
||||
*/
|
||||
DataFlow::LocalSourceNode clientRequestResponse(TypeTracker t, HTTP::Client::Request request) {
|
||||
DataFlow::LocalSourceNode clientRequestResponse(TypeTracker t, Http::Client::Request request) {
|
||||
t.start() and
|
||||
result = request.getResponseBody()
|
||||
or
|
||||
@@ -166,7 +166,7 @@ module InsecureDownload {
|
||||
* A url that is downloaded through an insecure connection, where the result ends up being saved to a sensitive location.
|
||||
*/
|
||||
class FileWriteSink extends Sink {
|
||||
HTTP::Client::Request request;
|
||||
Http::Client::Request request;
|
||||
|
||||
FileWriteSink() {
|
||||
// For example, in:
|
||||
@@ -193,6 +193,6 @@ module InsecureDownload {
|
||||
|
||||
override DataFlow::FlowState getALabel() { result instanceof Label::Insecure }
|
||||
|
||||
override DataFlow::Node getDownloadCall() { result.asExpr().getExpr() = request }
|
||||
override DataFlow::Node getDownloadCall() { result = request }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,10 @@ class OverlyWideRange extends RegExpCharacterRange {
|
||||
toCodePoint("A") <= high
|
||||
or
|
||||
// a non-alphanumeric char as part of the range boundaries
|
||||
exists(int bound | bound = [low, high] | not isAlphanumeric(bound.toUnicode()))
|
||||
exists(int bound | bound = [low, high] | not isAlphanumeric(bound.toUnicode())) and
|
||||
// while still being ascii
|
||||
low < 128 and
|
||||
high < 128
|
||||
) and
|
||||
// allowlist for known ranges
|
||||
not this = allowedWideRanges()
|
||||
@@ -173,7 +176,7 @@ module RangePrinter {
|
||||
}
|
||||
|
||||
/** Gets the number of parts we should print for a given `range`. */
|
||||
private int parts(OverlyWideRange range) { result = 1 + strictcount(cutoff(range, _)) }
|
||||
private int parts(OverlyWideRange range) { result = 1 + count(cutoff(range, _)) }
|
||||
|
||||
/** Holds if the given part of a range should span from `low` to `high`. */
|
||||
private predicate part(OverlyWideRange range, int part, string low, string high) {
|
||||
|
||||
@@ -45,7 +45,7 @@ module ServerSideRequestForgery {
|
||||
|
||||
/** The URL of an HTTP request, considered as a sink. */
|
||||
class HttpRequestAsSink extends Sink {
|
||||
HttpRequestAsSink() { exists(HTTP::Client::Request req | req.getAUrlPart() = this) }
|
||||
HttpRequestAsSink() { exists(Http::Client::Request req | req.getAUrlPart() = this) }
|
||||
}
|
||||
|
||||
/** A string interpolation with a fixed prefix, considered as a flow sanitizer. */
|
||||
|
||||
@@ -57,7 +57,7 @@ module UrlRedirect {
|
||||
*/
|
||||
class RedirectLocationAsSink extends Sink {
|
||||
RedirectLocationAsSink() {
|
||||
exists(HTTP::Server::HttpRedirectResponse e, MethodBase method |
|
||||
exists(Http::Server::HttpRedirectResponse e, MethodBase method |
|
||||
this = e.getRedirectLocation() and
|
||||
// We only want handlers for GET requests.
|
||||
// Handlers for other HTTP methods are not as vulnerable to URL
|
||||
|
||||
@@ -115,6 +115,7 @@ private newtype TStatePair =
|
||||
private int rankState(State state) {
|
||||
state =
|
||||
rank[result](State s, Location l |
|
||||
stateInsideBacktracking(s) and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s order by l.getStartLine(), l.getStartColumn(), s.toString()
|
||||
|
||||
@@ -93,8 +93,6 @@ class RegExpRoot extends RegExpTerm {
|
||||
* Holds if this root term is relevant to the ReDoS analysis.
|
||||
*/
|
||||
predicate isRelevant() {
|
||||
// there is at least one repetition
|
||||
getRoot(any(InfiniteRepetitionQuantifier q)) = this and
|
||||
// is actually used as a RegExp
|
||||
this.isUsedAsRegExp() and
|
||||
// not excluded for library specific reasons
|
||||
@@ -877,6 +875,107 @@ predicate isStartState(State state) {
|
||||
*/
|
||||
signature predicate isCandidateSig(State state, string pump);
|
||||
|
||||
/**
|
||||
* Holds if `state` is a candidate for ReDoS.
|
||||
*/
|
||||
signature predicate isCandidateSig(State state);
|
||||
|
||||
/**
|
||||
* Predicates for constructing a prefix string that leads to a given state.
|
||||
*/
|
||||
module PrefixConstruction<isCandidateSig/1 isCandidate> {
|
||||
/**
|
||||
* Holds if `state` is the textually last start state for the regular expression.
|
||||
*/
|
||||
private predicate lastStartState(RelevantState state) {
|
||||
exists(RegExpRoot root |
|
||||
state =
|
||||
max(RelevantState s, Location l |
|
||||
isStartState(s) and
|
||||
getRoot(s.getRepr()) = root and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s
|
||||
order by
|
||||
l.getStartLine(), l.getStartColumn(), s.getRepr().toString(), l.getEndColumn(),
|
||||
l.getEndLine()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists any transition (Epsilon() or other) from `a` to `b`.
|
||||
*/
|
||||
private predicate existsTransition(State a, State b) { delta(a, _, b) }
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the `start` state.
|
||||
*/
|
||||
int prefixLength(State start, State state) =
|
||||
shortestDistances(lastStartState/1, existsTransition/2)(start, state, result)
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the start state.
|
||||
*/
|
||||
private int lengthFromStart(State state) { result = prefixLength(_, state) }
|
||||
|
||||
/**
|
||||
* Gets a string for which the regular expression will reach `state`.
|
||||
*
|
||||
* Has at most one result for any given `state`.
|
||||
* This predicate will not always have a result even if there is a ReDoS issue in
|
||||
* the regular expression.
|
||||
*/
|
||||
string prefix(State state) {
|
||||
lastStartState(state) and
|
||||
result = ""
|
||||
or
|
||||
// the search stops past the last redos candidate state.
|
||||
lengthFromStart(state) <= max(lengthFromStart(any(State s | isCandidate(s)))) and
|
||||
exists(State prev |
|
||||
// select a unique predecessor (by an arbitrary measure)
|
||||
prev =
|
||||
min(State s, Location loc |
|
||||
lengthFromStart(s) = lengthFromStart(state) - 1 and
|
||||
loc = s.getRepr().getLocation() and
|
||||
delta(s, _, state)
|
||||
|
|
||||
s
|
||||
order by
|
||||
loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(),
|
||||
s.getRepr().toString()
|
||||
)
|
||||
|
|
||||
// greedy search for the shortest prefix
|
||||
result = prefix(prev) and delta(prev, Epsilon(), state)
|
||||
or
|
||||
not delta(prev, Epsilon(), state) and
|
||||
result = prefix(prev) + getCanonicalEdgeChar(prev, state)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a canonical char for which there exists a transition from `prev` to `next` in the NFA.
|
||||
*/
|
||||
private string getCanonicalEdgeChar(State prev, State next) {
|
||||
result =
|
||||
min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next))
|
||||
}
|
||||
|
||||
/** A state within a regular expression that contains a candidate state. */
|
||||
class RelevantState instanceof State {
|
||||
RelevantState() {
|
||||
exists(State s | isCandidate(s) | getRoot(s.getRepr()) = getRoot(this.getRepr()))
|
||||
}
|
||||
|
||||
/** Gets a string representation for this state in a regular expression. */
|
||||
string toString() { result = State.super.toString() }
|
||||
|
||||
/** Gets the term represented by this state. */
|
||||
RegExpTerm getRepr() { result = State.super.getRepr() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for pruning candidate ReDoS states.
|
||||
* The candidates are specified by the `isCandidate` signature predicate.
|
||||
@@ -910,95 +1009,11 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
/** Gets a state that can reach the `accept-any` state using only epsilon steps. */
|
||||
private State acceptsAnySuffix() { epsilonSucc*(result) = AcceptAnySuffix(_) }
|
||||
|
||||
/**
|
||||
* Predicates for constructing a prefix string that leads to a given state.
|
||||
*/
|
||||
private module PrefixConstruction {
|
||||
/**
|
||||
* 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 |
|
||||
s = stateInPumpableRegexp() and
|
||||
isStartState(s) and
|
||||
getRoot(s.getRepr()) = root and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s
|
||||
order by
|
||||
l.getStartLine(), l.getStartColumn(), s.getRepr().toString(), l.getEndColumn(),
|
||||
l.getEndLine()
|
||||
)
|
||||
)
|
||||
}
|
||||
predicate isCandidateState(State s) { isReDoSCandidate(s, _) }
|
||||
|
||||
/**
|
||||
* Holds if there exists any transition (Epsilon() or other) from `a` to `b`.
|
||||
*/
|
||||
private predicate existsTransition(State a, State b) { delta(a, _, b) }
|
||||
import PrefixConstruction<isCandidateState/1> as Prefix
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the `start` state.
|
||||
*/
|
||||
int prefixLength(State start, State state) =
|
||||
shortestDistances(lastStartState/1, existsTransition/2)(start, state, result)
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the start state.
|
||||
*/
|
||||
private int lengthFromStart(State state) { result = prefixLength(_, state) }
|
||||
|
||||
/**
|
||||
* Gets a string for which the regular expression will reach `state`.
|
||||
*
|
||||
* Has at most one result for any given `state`.
|
||||
* This predicate will not always have a result even if there is a ReDoS issue in
|
||||
* the regular expression.
|
||||
*/
|
||||
string prefix(State state) {
|
||||
lastStartState(state) and
|
||||
result = ""
|
||||
or
|
||||
// the search stops past the last redos candidate state.
|
||||
lengthFromStart(state) <= max(lengthFromStart(any(State s | isReDoSCandidate(s, _)))) and
|
||||
exists(State prev |
|
||||
// select a unique predecessor (by an arbitrary measure)
|
||||
prev =
|
||||
min(State s, Location loc |
|
||||
lengthFromStart(s) = lengthFromStart(state) - 1 and
|
||||
loc = s.getRepr().getLocation() and
|
||||
delta(s, _, state)
|
||||
|
|
||||
s
|
||||
order by
|
||||
loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(),
|
||||
s.getRepr().toString()
|
||||
)
|
||||
|
|
||||
// greedy search for the shortest prefix
|
||||
result = prefix(prev) and delta(prev, Epsilon(), state)
|
||||
or
|
||||
not delta(prev, Epsilon(), state) and
|
||||
result = prefix(prev) + getCanonicalEdgeChar(prev, state)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a canonical char for which there exists a transition from `prev` to `next` in the NFA.
|
||||
*/
|
||||
private string getCanonicalEdgeChar(State prev, State next) {
|
||||
result =
|
||||
min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next))
|
||||
}
|
||||
|
||||
/** Gets a state within a regular expression that has a pumpable state. */
|
||||
pragma[noinline]
|
||||
State stateInPumpableRegexp() {
|
||||
exists(State s | isReDoSCandidate(s, _) | getRoot(s.getRepr()) = getRoot(result.getRepr()))
|
||||
}
|
||||
}
|
||||
class RelevantState = Prefix::RelevantState;
|
||||
|
||||
/**
|
||||
* Predicates for testing the presence of a rejecting suffix.
|
||||
@@ -1018,8 +1033,6 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes.
|
||||
*/
|
||||
private module SuffixConstruction {
|
||||
import PrefixConstruction
|
||||
|
||||
/**
|
||||
* Holds if all states reachable from `fork` by repeating `w`
|
||||
* are likely rejectable by appending some suffix.
|
||||
@@ -1035,32 +1048,26 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* This predicate might find impossible suffixes when searching for suffixes of length > 1, which can cause FPs.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate isLikelyRejectable(State s) {
|
||||
s = stateInPumpableRegexp() and
|
||||
(
|
||||
// exists a reject edge with some char.
|
||||
hasRejectEdge(s)
|
||||
or
|
||||
hasEdgeToLikelyRejectable(s)
|
||||
or
|
||||
// stopping here is rejection
|
||||
isRejectState(s)
|
||||
)
|
||||
private predicate isLikelyRejectable(RelevantState s) {
|
||||
// exists a reject edge with some char.
|
||||
hasRejectEdge(s)
|
||||
or
|
||||
hasEdgeToLikelyRejectable(s)
|
||||
or
|
||||
// stopping here is rejection
|
||||
isRejectState(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `s` is not an accept state, and there is no epsilon transition to an accept state.
|
||||
*/
|
||||
predicate isRejectState(State s) {
|
||||
s = stateInPumpableRegexp() and not epsilonSucc*(s) = Accept(_)
|
||||
}
|
||||
predicate isRejectState(RelevantState s) { not epsilonSucc*(s) = Accept(_) }
|
||||
|
||||
/**
|
||||
* Holds if there is likely a non-empty suffix leading to rejection starting in `s`.
|
||||
*/
|
||||
pragma[noopt]
|
||||
predicate hasEdgeToLikelyRejectable(State s) {
|
||||
s = stateInPumpableRegexp() and
|
||||
predicate hasEdgeToLikelyRejectable(RelevantState s) {
|
||||
// all edges (at least one) with some char leads to another state that is rejectable.
|
||||
// the `next` states might not share a common suffix, which can cause FPs.
|
||||
exists(string char | char = hasEdgeToLikelyRejectableHelper(s) |
|
||||
@@ -1075,8 +1082,7 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* and `s` has not been found to be rejectable by `hasRejectEdge` or `isRejectState`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private string hasEdgeToLikelyRejectableHelper(State s) {
|
||||
s = stateInPumpableRegexp() and
|
||||
private string hasEdgeToLikelyRejectableHelper(RelevantState s) {
|
||||
not hasRejectEdge(s) and
|
||||
not isRejectState(s) and
|
||||
deltaClosedChar(s, result, _)
|
||||
@@ -1087,9 +1093,7 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* along epsilon edges, such that there is a transition from
|
||||
* `prev` to `next` that the character symbol `char`.
|
||||
*/
|
||||
predicate deltaClosedChar(State prev, string char, State next) {
|
||||
prev = stateInPumpableRegexp() and
|
||||
next = stateInPumpableRegexp() and
|
||||
predicate deltaClosedChar(RelevantState prev, string char, RelevantState next) {
|
||||
deltaClosed(prev, getAnInputSymbolMatchingRelevant(char), next)
|
||||
}
|
||||
|
||||
@@ -1099,18 +1103,28 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
result = getAnInputSymbolMatching(char)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
RegExpRoot relevantRoot() {
|
||||
exists(RegExpTerm term, State s |
|
||||
s.getRepr() = term and isCandidateState(s) and result = term.getRootTerm()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a char used for finding possible suffixes inside `root`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private string relevant(RegExpRoot root) {
|
||||
exists(ascii(result)) and exists(root)
|
||||
or
|
||||
exists(InputSymbol s | belongsTo(s, root) | result = intersect(s, _))
|
||||
or
|
||||
// The characters from `hasSimpleRejectEdge`. Only `\n` is really needed (as `\n` is not in the `ascii` relation).
|
||||
// The three chars must be kept in sync with `hasSimpleRejectEdge`.
|
||||
result = ["|", "\n", "Z"] and exists(root)
|
||||
root = relevantRoot() and
|
||||
(
|
||||
exists(ascii(result)) and exists(root)
|
||||
or
|
||||
exists(InputSymbol s | belongsTo(s, root) | result = intersect(s, _))
|
||||
or
|
||||
// The characters from `hasSimpleRejectEdge`. Only `\n` is really needed (as `\n` is not in the `ascii` relation).
|
||||
// The three chars must be kept in sync with `hasSimpleRejectEdge`.
|
||||
result = ["|", "\n", "Z"] and exists(root)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1208,12 +1222,12 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
|
||||
isReDoSAttackable(t, pump, s) and
|
||||
(
|
||||
prefixMsg = "starting with '" + escape(PrefixConstruction::prefix(s)) + "' and " and
|
||||
not PrefixConstruction::prefix(s) = ""
|
||||
prefixMsg = "starting with '" + escape(Prefix::prefix(s)) + "' and " and
|
||||
not Prefix::prefix(s) = ""
|
||||
or
|
||||
PrefixConstruction::prefix(s) = "" and prefixMsg = ""
|
||||
Prefix::prefix(s) = "" and prefixMsg = ""
|
||||
or
|
||||
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
|
||||
not exists(Prefix::prefix(s)) and prefixMsg = ""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -63,14 +63,6 @@ module RegExpFlags {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -67,10 +67,12 @@ private predicate viableParam(
|
||||
)
|
||||
}
|
||||
|
||||
private predicate callStep(ExprNodes::CallCfgNode call, Node nodeFrom, Node nodeTo) {
|
||||
/** Holds if there is flow from `arg` to `p` via the call `call`. */
|
||||
pragma[nomagic]
|
||||
predicate callStep(ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::ParameterNodeImpl p) {
|
||||
exists(DataFlowDispatch::ParameterPosition pos |
|
||||
argumentPositionMatch(call, nodeFrom, pos) and
|
||||
viableParam(call, nodeTo, pos)
|
||||
argumentPositionMatch(call, arg, pos) and
|
||||
viableParam(call, p, pos)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,3 +5,5 @@ extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
upgrades: upgrades
|
||||
library: true
|
||||
dependencies:
|
||||
codeql/ssa: 0.0.1
|
||||
|
||||
@@ -247,20 +247,16 @@ ruby_binary_def(
|
||||
int right: @ruby_underscore_expression ref
|
||||
);
|
||||
|
||||
ruby_block_body(
|
||||
unique int ruby_block: @ruby_block ref,
|
||||
unique int body: @ruby_block_body ref
|
||||
);
|
||||
|
||||
ruby_block_parameters(
|
||||
unique int ruby_block: @ruby_block ref,
|
||||
unique int parameters: @ruby_block_parameters ref
|
||||
);
|
||||
|
||||
@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_block, index]
|
||||
ruby_block_child(
|
||||
int ruby_block: @ruby_block ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_block_child_type ref
|
||||
);
|
||||
|
||||
ruby_block_def(
|
||||
unique int id: @ruby_block
|
||||
);
|
||||
@@ -274,6 +270,19 @@ ruby_block_argument_def(
|
||||
unique int id: @ruby_block_argument
|
||||
);
|
||||
|
||||
@ruby_block_body_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_block_body, index]
|
||||
ruby_block_body_child(
|
||||
int ruby_block_body: @ruby_block_body ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_block_body_child_type ref
|
||||
);
|
||||
|
||||
ruby_block_body_def(
|
||||
unique int id: @ruby_block_body
|
||||
);
|
||||
|
||||
ruby_block_parameter_name(
|
||||
unique int ruby_block_parameter: @ruby_block_parameter ref,
|
||||
unique int name: @ruby_token_identifier ref
|
||||
@@ -303,6 +312,19 @@ ruby_block_parameters_def(
|
||||
unique int id: @ruby_block_parameters
|
||||
);
|
||||
|
||||
@ruby_body_statement_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_body_statement, index]
|
||||
ruby_body_statement_child(
|
||||
int ruby_body_statement: @ruby_body_statement ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_body_statement_child_type ref
|
||||
);
|
||||
|
||||
ruby_body_statement_def(
|
||||
unique int id: @ruby_body_statement
|
||||
);
|
||||
|
||||
ruby_break_child(
|
||||
unique int ruby_break: @ruby_break ref,
|
||||
unique int child: @ruby_argument_list ref
|
||||
@@ -391,6 +413,11 @@ ruby_chained_string_def(
|
||||
unique int id: @ruby_chained_string
|
||||
);
|
||||
|
||||
ruby_class_body(
|
||||
unique int ruby_class: @ruby_class ref,
|
||||
unique int body: @ruby_body_statement ref
|
||||
);
|
||||
|
||||
@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant
|
||||
|
||||
ruby_class_superclass(
|
||||
@@ -398,15 +425,6 @@ ruby_class_superclass(
|
||||
unique int superclass: @ruby_superclass ref
|
||||
);
|
||||
|
||||
@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_class, index]
|
||||
ruby_class_child(
|
||||
int ruby_class: @ruby_class ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_class_child_type ref
|
||||
);
|
||||
|
||||
ruby_class_def(
|
||||
unique int id: @ruby_class,
|
||||
int name: @ruby_class_name_type ref
|
||||
@@ -478,20 +496,16 @@ ruby_do_def(
|
||||
unique int id: @ruby_do
|
||||
);
|
||||
|
||||
ruby_do_block_body(
|
||||
unique int ruby_do_block: @ruby_do_block ref,
|
||||
unique int body: @ruby_body_statement ref
|
||||
);
|
||||
|
||||
ruby_do_block_parameters(
|
||||
unique int ruby_do_block: @ruby_do_block ref,
|
||||
unique int parameters: @ruby_block_parameters ref
|
||||
);
|
||||
|
||||
@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_do_block, index]
|
||||
ruby_do_block_child(
|
||||
int ruby_do_block: @ruby_do_block ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_do_block_child_type ref
|
||||
);
|
||||
|
||||
ruby_do_block_def(
|
||||
unique int id: @ruby_do_block
|
||||
);
|
||||
@@ -724,7 +738,7 @@ ruby_in_clause_def(
|
||||
int pattern: @ruby_underscore_pattern_top_expr_body ref
|
||||
);
|
||||
|
||||
@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_nonlocal_variable | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_interpolation, index]
|
||||
ruby_interpolation_child(
|
||||
@@ -797,20 +811,18 @@ ruby_left_assignment_list_def(
|
||||
unique int id: @ruby_left_assignment_list
|
||||
);
|
||||
|
||||
@ruby_method_body_type = @ruby_body_statement | @ruby_rescue_modifier | @ruby_underscore_arg
|
||||
|
||||
ruby_method_body(
|
||||
unique int ruby_method: @ruby_method ref,
|
||||
unique int body: @ruby_method_body_type ref
|
||||
);
|
||||
|
||||
ruby_method_parameters(
|
||||
unique int ruby_method: @ruby_method ref,
|
||||
unique int parameters: @ruby_method_parameters ref
|
||||
);
|
||||
|
||||
@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_arg | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_method, index]
|
||||
ruby_method_child(
|
||||
int ruby_method: @ruby_method ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_method_child_type ref
|
||||
);
|
||||
|
||||
ruby_method_def(
|
||||
unique int id: @ruby_method,
|
||||
int name: @ruby_underscore_method_name ref
|
||||
@@ -829,17 +841,13 @@ ruby_method_parameters_def(
|
||||
unique int id: @ruby_method_parameters
|
||||
);
|
||||
|
||||
@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant
|
||||
|
||||
@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_module, index]
|
||||
ruby_module_child(
|
||||
int ruby_module: @ruby_module ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_module_child_type ref
|
||||
ruby_module_body(
|
||||
unique int ruby_module: @ruby_module ref,
|
||||
unique int body: @ruby_body_statement ref
|
||||
);
|
||||
|
||||
@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant
|
||||
|
||||
ruby_module_def(
|
||||
unique int id: @ruby_module,
|
||||
int name: @ruby_module_name_type ref
|
||||
@@ -1074,13 +1082,9 @@ ruby_setter_def(
|
||||
int name: @ruby_token_identifier ref
|
||||
);
|
||||
|
||||
@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_singleton_class, index]
|
||||
ruby_singleton_class_child(
|
||||
int ruby_singleton_class: @ruby_singleton_class ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_singleton_class_child_type ref
|
||||
ruby_singleton_class_body(
|
||||
unique int ruby_singleton_class: @ruby_singleton_class ref,
|
||||
unique int body: @ruby_body_statement ref
|
||||
);
|
||||
|
||||
ruby_singleton_class_def(
|
||||
@@ -1088,6 +1092,13 @@ ruby_singleton_class_def(
|
||||
int value: @ruby_underscore_arg ref
|
||||
);
|
||||
|
||||
@ruby_singleton_method_body_type = @ruby_body_statement | @ruby_rescue_modifier | @ruby_underscore_arg
|
||||
|
||||
ruby_singleton_method_body(
|
||||
unique int ruby_singleton_method: @ruby_singleton_method ref,
|
||||
unique int body: @ruby_singleton_method_body_type ref
|
||||
);
|
||||
|
||||
@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable
|
||||
|
||||
ruby_singleton_method_parameters(
|
||||
@@ -1095,15 +1106,6 @@ ruby_singleton_method_parameters(
|
||||
unique int parameters: @ruby_method_parameters ref
|
||||
);
|
||||
|
||||
@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_arg | @ruby_underscore_statement
|
||||
|
||||
#keyset[ruby_singleton_method, index]
|
||||
ruby_singleton_method_child(
|
||||
int ruby_singleton_method: @ruby_singleton_method ref,
|
||||
int index: int ref,
|
||||
unique int child: @ruby_singleton_method_child_type ref
|
||||
);
|
||||
|
||||
ruby_singleton_method_def(
|
||||
unique int id: @ruby_singleton_method,
|
||||
int name: @ruby_underscore_method_name ref,
|
||||
@@ -1344,7 +1346,7 @@ case @ruby_token.kind of
|
||||
;
|
||||
|
||||
|
||||
@ruby_ast_node = @ruby_alias | @ruby_alternative_pattern | @ruby_argument_list | @ruby_array | @ruby_array_pattern | @ruby_as_pattern | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_case_match | @ruby_chained_string | @ruby_class | @ruby_complex | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_expression_reference_pattern | @ruby_find_pattern | @ruby_for | @ruby_hash | @ruby_hash_pattern | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_guard | @ruby_if_modifier | @ruby_in | @ruby_in_clause | @ruby_interpolation | @ruby_keyword_parameter | @ruby_keyword_pattern | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_pattern | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_guard | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_variable_reference_pattern | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield
|
||||
@ruby_ast_node = @ruby_alias | @ruby_alternative_pattern | @ruby_argument_list | @ruby_array | @ruby_array_pattern | @ruby_as_pattern | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_body | @ruby_block_parameter | @ruby_block_parameters | @ruby_body_statement | @ruby_break | @ruby_call | @ruby_case__ | @ruby_case_match | @ruby_chained_string | @ruby_class | @ruby_complex | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_expression_reference_pattern | @ruby_find_pattern | @ruby_for | @ruby_hash | @ruby_hash_pattern | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_guard | @ruby_if_modifier | @ruby_in | @ruby_in_clause | @ruby_interpolation | @ruby_keyword_parameter | @ruby_keyword_pattern | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_pattern | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_guard | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_variable_reference_pattern | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield
|
||||
|
||||
@ruby_ast_node_parent = @file | @ruby_ast_node
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Location extends @location {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class File extends @file {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
private predicate body_statement(AstNode body, int index, Location loc) {
|
||||
exists(AstNode node, AstNode child | ruby_ast_node_info(child, _, _, loc) |
|
||||
ruby_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_class_child(node, index, child)
|
||||
or
|
||||
ruby_do_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_do_block_child(node, index, child)
|
||||
or
|
||||
ruby_method_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_method_child(node, index, child)
|
||||
or
|
||||
ruby_module_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_module_child(node, index, child)
|
||||
or
|
||||
ruby_singleton_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_class_child(node, index, child)
|
||||
or
|
||||
ruby_singleton_method_def(node, _, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_method_child(node, index, child)
|
||||
or
|
||||
ruby_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "}") and
|
||||
ruby_block_child(node, index, child)
|
||||
)
|
||||
}
|
||||
|
||||
from Location loc, File file, int start_line, int start_column, int end_line, int end_column
|
||||
where
|
||||
locations_default(loc, file, start_line, start_column, end_line, end_column) and
|
||||
not exists(AstNode node | ruby_ast_node_info(node, _, _, loc) and body_statement(node, _, _))
|
||||
or
|
||||
exists(AstNode node |
|
||||
ruby_ast_node_info(node, _, _, loc) and
|
||||
exists(Location first |
|
||||
body_statement(node, 0, first) and
|
||||
locations_default(first, pragma[only_bind_into](file), start_line, start_column, _, _)
|
||||
) and
|
||||
exists(Location last |
|
||||
last = max(Location l, int i | body_statement(node, i, l) | l order by i) and
|
||||
locations_default(last, pragma[only_bind_into](file), _, _, end_line, end_column)
|
||||
)
|
||||
)
|
||||
select loc, file, start_line, start_column, end_line, end_column
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
||||
class AstNode extends @ruby_ast_node_parent {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Location extends @location {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
private predicate body_statement(AstNode body, AstNode firstChild) {
|
||||
exists(AstNode node |
|
||||
ruby_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_class_child(node, 0, firstChild)
|
||||
or
|
||||
ruby_do_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_do_block_child(node, 0, firstChild)
|
||||
or
|
||||
ruby_method_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_method_child(node, 0, firstChild)
|
||||
or
|
||||
ruby_module_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_module_child(node, 0, firstChild)
|
||||
or
|
||||
ruby_singleton_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_class_child(node, 0, firstChild)
|
||||
or
|
||||
ruby_singleton_method_def(node, _, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_method_child(node, 0, firstChild)
|
||||
or
|
||||
ruby_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "}") and
|
||||
ruby_block_child(node, 0, firstChild)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate body_statement_child(AstNode body, int index, AstNode child) {
|
||||
exists(AstNode parent, AstNode firstChild, int firstChildIndex |
|
||||
body_statement(body, firstChild) and
|
||||
ruby_ast_node_info(firstChild, parent, firstChildIndex, _) and
|
||||
child =
|
||||
rank[index + 1](AstNode c, int i |
|
||||
ruby_ast_node_info(c, parent, i, _) and i >= firstChildIndex and c != body
|
||||
|
|
||||
c order by i
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate astNodeInfo(AstNode node, AstNode parent, int parent_index, Location loc) {
|
||||
ruby_ast_node_info(node, parent, parent_index, loc) and
|
||||
not body_statement(node, _) and
|
||||
not body_statement_child(_, _, node)
|
||||
}
|
||||
|
||||
from AstNode node, AstNode parent, int parent_index, Location loc
|
||||
where
|
||||
astNodeInfo(node, parent, parent_index, loc)
|
||||
or
|
||||
body_statement_child(parent, parent_index, node) and ruby_ast_node_info(node, _, _, loc)
|
||||
or
|
||||
body_statement(node, _) and
|
||||
ruby_ast_node_info(node, parent, _, loc) and
|
||||
parent_index = count(AstNode n | astNodeInfo(n, parent, _, _))
|
||||
select node, parent, parent_index, loc
|
||||
@@ -0,0 +1,11 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_block, AstNode body
|
||||
where
|
||||
ruby_block_def(ruby_block) and
|
||||
ruby_ast_node_info(body, ruby_block, _, _) and
|
||||
ruby_tokeninfo(body, _, "}") and
|
||||
ruby_block_child(ruby_block, _, _)
|
||||
select ruby_block, body
|
||||
@@ -0,0 +1,11 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode ruby_block, AstNode body, int index, AstNode child
|
||||
where
|
||||
ruby_block_def(ruby_block) and
|
||||
ruby_ast_node_info(body, ruby_block, _, _) and
|
||||
ruby_tokeninfo(body, _, "}") and
|
||||
ruby_block_child(ruby_block, index, child)
|
||||
select body, index, child
|
||||
@@ -0,0 +1,18 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/*
|
||||
* It's not possible to generate fresh IDs for the new ruby_block_body nodes,
|
||||
* therefore we re-purpose the "}"-token that closes the block and use its ID instead.
|
||||
* As a result the AST will be missing the "}" tokens, but those are unlikely to be used
|
||||
* for anything.
|
||||
*/
|
||||
|
||||
from AstNode ruby_block, AstNode body
|
||||
where
|
||||
ruby_block_def(ruby_block) and
|
||||
ruby_ast_node_info(body, ruby_block, _, _) and
|
||||
ruby_tokeninfo(body, _, "}") and
|
||||
ruby_block_child(ruby_block, _, _)
|
||||
select body
|
||||
@@ -0,0 +1,36 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body, int index, AstNode child
|
||||
where
|
||||
ruby_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_class_child(node, index, child)
|
||||
or
|
||||
ruby_do_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_do_block_child(node, index, child)
|
||||
or
|
||||
ruby_method_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_method_child(node, index, child)
|
||||
or
|
||||
ruby_module_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_module_child(node, index, child)
|
||||
or
|
||||
ruby_singleton_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_class_child(node, index, child)
|
||||
or
|
||||
ruby_singleton_method_def(node, _, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_method_child(node, index, child)
|
||||
select body, index, child
|
||||
@@ -0,0 +1,43 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/*
|
||||
* It's not possible to generate fresh IDs for the new ruby_body_statement nodes,
|
||||
* therefore we re-purpose the "end"-token that closes the block and use its ID instead.
|
||||
* As a result the AST will be missing the "end" tokens, but those are unlikely to be used
|
||||
* for anything.
|
||||
*/
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_class_child(node, _, _)
|
||||
or
|
||||
ruby_do_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_do_block_child(node, _, _)
|
||||
or
|
||||
ruby_method_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_method_child(node, _, _)
|
||||
or
|
||||
ruby_module_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_module_child(node, _, _)
|
||||
or
|
||||
ruby_singleton_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_class_child(node, _, _)
|
||||
or
|
||||
ruby_singleton_method_def(node, _, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_method_child(node, _, _)
|
||||
select body
|
||||
@@ -0,0 +1,11 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_class_child(node, _, _)
|
||||
select node, body
|
||||
@@ -0,0 +1,11 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_do_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_do_block_child(node, _, _)
|
||||
select node, body
|
||||
@@ -0,0 +1,20 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_method_def(node, _) and
|
||||
(
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_method_child(node, _, _)
|
||||
or
|
||||
ruby_method_child(node, 0, body) and
|
||||
not exists(AstNode n |
|
||||
ruby_ast_node_info(n, node, _, _) and
|
||||
ruby_tokeninfo(n, _, "end")
|
||||
)
|
||||
)
|
||||
// TODO : handle end-less methods
|
||||
select node, body
|
||||
@@ -0,0 +1,11 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_module_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_module_child(node, _, _)
|
||||
select node, body
|
||||
@@ -0,0 +1,11 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_singleton_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_class_child(node, _, _)
|
||||
select node, body
|
||||
@@ -0,0 +1,17 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNode node, AstNode body
|
||||
where
|
||||
ruby_singleton_method_def(node, _, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_method_child(node, _, _)
|
||||
or
|
||||
ruby_singleton_method_child(node, 0, body) and
|
||||
not exists(AstNode n |
|
||||
ruby_ast_node_info(n, node, _, _) and
|
||||
ruby_tokeninfo(n, _, "end")
|
||||
)
|
||||
select node, body
|
||||
@@ -0,0 +1,46 @@
|
||||
class AstNode extends @ruby_ast_node {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
private predicate body_statement(AstNode body) {
|
||||
exists(AstNode node |
|
||||
ruby_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_class_child(node, _, _)
|
||||
or
|
||||
ruby_do_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_do_block_child(node, _, _)
|
||||
or
|
||||
ruby_method_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_method_child(node, _, _)
|
||||
or
|
||||
ruby_module_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_module_child(node, _, _)
|
||||
or
|
||||
ruby_singleton_class_def(node, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_class_child(node, _, _)
|
||||
or
|
||||
ruby_singleton_method_def(node, _, _) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "end") and
|
||||
ruby_singleton_method_child(node, _, _)
|
||||
or
|
||||
ruby_block_def(node) and
|
||||
ruby_ast_node_info(body, node, _, _) and
|
||||
ruby_tokeninfo(body, _, "}") and
|
||||
ruby_block_child(node, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
from AstNode token, int kind, string value
|
||||
where ruby_tokeninfo(token, kind, value) and not body_statement(token)
|
||||
select token, kind, value
|
||||
@@ -0,0 +1,33 @@
|
||||
description: Wrap class, module, method, and block bodies in a named node
|
||||
compatibility: partial
|
||||
|
||||
ruby_block_body.rel: run ruby_block_body.qlo
|
||||
ruby_block_body_def.rel: run ruby_block_body_def.qlo
|
||||
ruby_block_body_child.rel: run ruby_block_body_child.qlo
|
||||
ruby_block_child.rel: delete
|
||||
|
||||
ruby_body_statement_child.rel: run ruby_body_statement_child.qlo
|
||||
ruby_body_statement_def.rel: run ruby_body_statement_def.qlo
|
||||
|
||||
ruby_class_body.rel: run ruby_class_body.qlo
|
||||
ruby_class_child.rel: delete
|
||||
|
||||
ruby_do_block_body.rel: run ruby_do_block_body.qlo
|
||||
ruby_do_block_child.rel: delete
|
||||
|
||||
ruby_method_body.rel: run ruby_method_body.qlo
|
||||
ruby_method_child.rel: delete
|
||||
|
||||
ruby_module_body.rel: run ruby_module_body.qlo
|
||||
ruby_module_child.rel: delete
|
||||
|
||||
ruby_singleton_class_body.rel: run ruby_singleton_class_body.qlo
|
||||
ruby_singleton_class_child.rel: delete
|
||||
|
||||
ruby_singleton_method_body.rel: run ruby_singleton_method_body.qlo
|
||||
ruby_singleton_method_child.rel: delete
|
||||
|
||||
ruby_ast_node_info.rel: run ruby_ast_node_info.qlo
|
||||
ruby_tokeninfo.rel: run ruby_tokeninfo.qlo
|
||||
|
||||
locations_default.rel: run locations_default.qlo
|
||||
4
ruby/ql/src/change-notes/2022-07-26-hardcoded-data.md
Normal file
4
ruby/ql/src/change-notes/2022-07-26-hardcoded-data.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rb/hardcoded-data-interpreted-as-code`, to detect cases where hardcoded data is executed as code, a technique associated with backdoors.
|
||||
4
ruby/ql/src/change-notes/2022-08-23-alert-messages.md
Normal file
4
ruby/ql/src/change-notes/2022-08-23-alert-messages.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The alert message of many queries have been changed to make the message consistent with other languages.
|
||||
@@ -22,5 +22,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is used in a path.", source.getNode(),
|
||||
"User-provided value"
|
||||
|
||||
@@ -20,4 +20,4 @@ import DataFlow::PathGraph
|
||||
from ReflectedXss::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "a user-provided value"
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -18,5 +18,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from StoredXss::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@",
|
||||
select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "stored value"
|
||||
|
||||
@@ -22,5 +22,5 @@ from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, S
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
sourceNode = source.getNode()
|
||||
select sink.getNode(), source, sink, "This code execution depends on $@.", sourceNode,
|
||||
"a user-provided value"
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is interpreted as code.",
|
||||
source.getNode(), "User-provided value"
|
||||
|
||||
@@ -15,6 +15,20 @@ import ruby
|
||||
import codeql.ruby.Concepts
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
from HTTP::Client::Request request, DataFlow::Node disablingNode
|
||||
where request.disablesCertificateValidation(disablingNode)
|
||||
select request, "This request may run with $@.", disablingNode, "certificate validation disabled"
|
||||
from
|
||||
Http::Client::Request request, DataFlow::Node disablingNode, DataFlow::Node origin, string ending
|
||||
where
|
||||
request.disablesCertificateValidation(disablingNode, origin) and
|
||||
// Showing the origin is only useful when it's a different node than the one disabling
|
||||
// certificate validation, for example in `requests.get(..., verify=arg)`, `arg` would
|
||||
// be the `disablingNode`, and the `origin` would be the place were `arg` got its
|
||||
// value from.
|
||||
//
|
||||
// NOTE: We compare the locations instead of DataFlow::Nodes directly, since for
|
||||
// snippet `Excon.defaults[:ssl_verify_peer] = false`, `disablingNode = argumentNode`
|
||||
// does NOT hold.
|
||||
if disablingNode.getLocation() = origin.getLocation()
|
||||
then ending = "."
|
||||
else ending = " by the value from $@."
|
||||
select request, "This request may run without certificate validation because it is $@" + ending,
|
||||
disablingNode, "disabled here", origin, "here"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user