mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
Merge branch 'main' into maikypedia/sqli-sink-2
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
4
ruby/ql/lib/change-notes/2023-05-06-pg.md
Normal file
4
ruby/ql/lib/change-notes/2023-05-06-pg.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Support for the `pg` gem has been added. Method calls that execute queries against a PostgreSQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.6.2
|
||||
|
||||
@@ -17,7 +17,7 @@ private string locationToString(Location loc) {
|
||||
*
|
||||
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
class Location extends @location {
|
||||
class Location extends @location_default {
|
||||
/** Gets the file for this location. */
|
||||
File getFile() { locations_default(this, result, _, _, _, _) }
|
||||
|
||||
|
||||
@@ -121,13 +121,15 @@ private Ruby::AstNode getSuperParent(Ruby::Super sup) {
|
||||
result = sup
|
||||
or
|
||||
result = getSuperParent(sup).getParent() and
|
||||
not result instanceof Ruby::Method
|
||||
not result instanceof Ruby::Method and
|
||||
not result instanceof Ruby::SingletonMethod
|
||||
}
|
||||
|
||||
private string getSuperMethodName(Ruby::Super sup) {
|
||||
exists(Ruby::Method meth |
|
||||
meth = getSuperParent(sup).getParent() and
|
||||
exists(Ruby::AstNode meth | meth = getSuperParent(sup).getParent() |
|
||||
result = any(Method c | toGenerated(c) = meth).getName()
|
||||
or
|
||||
result = any(SingletonMethod c | toGenerated(c) = meth).getName()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -936,10 +936,10 @@ module ExprNodes {
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
||||
class StringLiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
class StringLiteralCfgNode extends StringlikeLiteralCfgNode {
|
||||
StringLiteralCfgNode() { e instanceof StringLiteral }
|
||||
|
||||
override StringLiteral e;
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
|
||||
final override StringLiteral getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
@@ -9,6 +9,23 @@ private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
/**
|
||||
* A `LocalSourceNode` for a `self` variable. This is either an implicit `self`
|
||||
* parameter or an implicit SSA entry definition.
|
||||
*/
|
||||
private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
|
||||
private SelfVariable self;
|
||||
|
||||
SelfLocalSourceNode() {
|
||||
self = this.(SelfParameterNode).getSelfVariable()
|
||||
or
|
||||
self = this.(SsaSelfDefinitionNode).getVariable()
|
||||
}
|
||||
|
||||
/** Gets the `self` variable. */
|
||||
SelfVariable getVariable() { result = self }
|
||||
}
|
||||
|
||||
newtype TReturnKind =
|
||||
TNormalReturnKind() or
|
||||
TBreakReturnKind() or
|
||||
@@ -316,7 +333,7 @@ private predicate extendCall(DataFlow::ExprNode receiver, Module m) {
|
||||
exists(DataFlow::CallNode extendCall |
|
||||
extendCall.getMethodName() = "extend" and
|
||||
exists(DataFlow::LocalSourceNode sourceNode | sourceNode.flowsTo(extendCall.getArgument(_)) |
|
||||
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m) or
|
||||
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) or
|
||||
m = resolveConstantReadAccess(sourceNode.asExpr().getExpr())
|
||||
) and
|
||||
receiver = extendCall.getReceiver()
|
||||
@@ -329,7 +346,7 @@ private predicate extendCallModule(Module m, Module n) {
|
||||
exists(DataFlow::LocalSourceNode receiver, DataFlow::ExprNode e |
|
||||
receiver.flowsTo(e) and extendCall(e, n)
|
||||
|
|
||||
selfInModule(receiver.(SsaSelfDefinitionNode).getVariable(), m) or
|
||||
selfInModule(receiver.(SelfLocalSourceNode).getVariable(), m) or
|
||||
m = resolveConstantReadAccess(receiver.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
@@ -502,12 +519,12 @@ private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
|
||||
exact = true
|
||||
or
|
||||
// `self.new` inside a module
|
||||
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m) and
|
||||
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) and
|
||||
exact = true
|
||||
or
|
||||
// `self.new` inside a singleton method
|
||||
exists(MethodBase caller |
|
||||
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), caller, m) and
|
||||
selfInMethod(sourceNode.(SelfLocalSourceNode).getVariable(), caller, m) and
|
||||
singletonMethod(caller, _, _) and
|
||||
exact = false
|
||||
)
|
||||
@@ -573,7 +590,7 @@ private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
|
||||
// `self` reference in method or top-level (but not in module or singleton method,
|
||||
// where instance methods cannot be called; only singleton methods)
|
||||
n =
|
||||
any(SsaSelfDefinitionNode self |
|
||||
any(SelfLocalSourceNode self |
|
||||
exists(MethodBase m |
|
||||
selfInMethod(self.getVariable(), m, tp) and
|
||||
not m instanceof SingletonMethod and
|
||||
@@ -607,10 +624,10 @@ private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
|
||||
m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
// needed for e.g. `self.include`
|
||||
selfInModule(result.(SsaSelfDefinitionNode).getVariable(), m)
|
||||
selfInModule(result.(SelfLocalSourceNode).getVariable(), m)
|
||||
or
|
||||
// needed for e.g. `self.puts`
|
||||
selfInMethod(result.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), m)
|
||||
selfInMethod(result.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
|
||||
)
|
||||
)
|
||||
or
|
||||
@@ -970,7 +987,7 @@ private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string
|
||||
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */
|
||||
pragma[nomagic]
|
||||
private predicate selfInModuleFlowsToMethodCallReceiver(RelevantCall call, Module m, string method) {
|
||||
exists(SsaSelfDefinitionNode self |
|
||||
exists(SelfLocalSourceNode self |
|
||||
flowsToMethodCallReceiver(call, self, method) and
|
||||
selfInModule(self.getVariable(), m)
|
||||
)
|
||||
@@ -984,7 +1001,7 @@ pragma[nomagic]
|
||||
private predicate selfInSingletonMethodFlowsToMethodCallReceiver(
|
||||
RelevantCall call, Module m, string method
|
||||
) {
|
||||
exists(SsaSelfDefinitionNode self, MethodBase caller |
|
||||
exists(SelfLocalSourceNode self, MethodBase caller |
|
||||
flowsToMethodCallReceiver(call, self, method) and
|
||||
selfInMethod(self.getVariable(), caller, m) and
|
||||
singletonMethod(caller, _, _)
|
||||
@@ -1062,10 +1079,13 @@ private CfgScope getTargetSingleton(RelevantCall call, string method) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate argMustFlowToReceiver(
|
||||
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg,
|
||||
SsaDefinitionExtNode paramDef, RelevantCall call, Callable encl, string name
|
||||
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg, RelevantCall call,
|
||||
Callable encl, string name
|
||||
) {
|
||||
exists(ParameterNodeImpl p, ParameterPosition ppos, ArgumentPosition apos |
|
||||
exists(
|
||||
ParameterNodeImpl p, SsaDefinitionExtNode paramDef, ParameterPosition ppos,
|
||||
ArgumentPosition apos
|
||||
|
|
||||
// the receiver of `call` references `p`
|
||||
exists(DataFlow::Node receiver |
|
||||
LocalFlow::localFlowSsaParamInput(p, paramDef) and
|
||||
@@ -1106,7 +1126,7 @@ private predicate mayBenefitFromCallContextInitialize(
|
||||
RelevantCall ctx, RelevantCall new, DataFlow::Node arg, Callable encl, Module tp, string name
|
||||
) {
|
||||
exists(DataFlow::LocalSourceNode source |
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, new, encl, "new") and
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, new, encl, "new") and
|
||||
source = trackModuleAccess(tp) and
|
||||
name = "initialize" and
|
||||
exists(lookupMethod(tp, name))
|
||||
@@ -1127,7 +1147,7 @@ private predicate mayBenefitFromCallContextInstance(
|
||||
string name
|
||||
) {
|
||||
exists(DataFlow::LocalSourceNode source |
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, call, encl,
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, call, encl,
|
||||
pragma[only_bind_into](name)) and
|
||||
source = trackInstance(tp, exact) and
|
||||
exists(lookupMethod(tp, pragma[only_bind_into](name)))
|
||||
@@ -1148,7 +1168,7 @@ private predicate mayBenefitFromCallContextSingleton(
|
||||
string name
|
||||
) {
|
||||
exists(DataFlow::LocalSourceNode source |
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), _, call,
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), call,
|
||||
encl, pragma[only_bind_into](name)) and
|
||||
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
|
||||
|
|
||||
@@ -1216,13 +1236,10 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
or
|
||||
// `ctx` cannot provide a type bound, and the receiver of the call is `self`;
|
||||
// in this case, still apply an open-world assumption
|
||||
exists(
|
||||
RelevantCall call0, RelevantCall ctx0, DataFlow::Node arg, SsaSelfDefinitionNode self,
|
||||
string name
|
||||
|
|
||||
exists(RelevantCall call0, RelevantCall ctx0, DataFlow::Node arg, string name |
|
||||
call0 = call.asCall() and
|
||||
ctx0 = ctx.asCall() and
|
||||
argMustFlowToReceiver(ctx0, _, arg, self, call0, _, name) and
|
||||
argMustFlowToReceiver(ctx0, _, arg, call0, _, name) and
|
||||
not mayBenefitFromCallContextInitialize(ctx0, call0, arg, _, _, _) and
|
||||
not mayBenefitFromCallContextInstance(ctx0, call0, arg, _, _, _, name) and
|
||||
not mayBenefitFromCallContextSingleton(ctx0, call0, arg, _, _, _, name) and
|
||||
@@ -1230,7 +1247,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
)
|
||||
or
|
||||
// library calls should always be able to resolve
|
||||
argMustFlowToReceiver(ctx.asCall(), _, _, _, call.asCall(), _, _) and
|
||||
argMustFlowToReceiver(ctx.asCall(), _, _, call.asCall(), _, _) and
|
||||
result = viableLibraryCallable(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ module LocalFlow {
|
||||
exists(BasicBlock bb, int i |
|
||||
lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
|
||||
def = nodeFrom.getDefinitionExt() and
|
||||
def.definesAt(_, bb, i, _)
|
||||
def.definesAt(_, bb, i, _) and
|
||||
nodeFrom != next
|
||||
)
|
||||
}
|
||||
|
||||
@@ -143,12 +144,13 @@ module LocalFlow {
|
||||
* This is intended to recover from flow not currently recognised by ordinary capture flow.
|
||||
*/
|
||||
predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::CapturedEntryDefinition def |
|
||||
nodeFrom.asParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
|
|
||||
exists(Ssa::CapturedEntryDefinition def, ParameterNodeImpl p |
|
||||
(nodeFrom = p or LocalFlow::localFlowSsaParamInput(p, nodeFrom)) and
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
|
|
||||
p.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
or
|
||||
p.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -939,6 +941,12 @@ private class NewCall extends DataFlowCall {
|
||||
abstract class ReturningNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
|
||||
pragma[nomagic]
|
||||
predicate hasKind(ReturnKind kind, CfgScope scope) {
|
||||
kind = this.getKind() and
|
||||
scope = this.(NodeImpl).getCfgScope()
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
@@ -1059,10 +1067,8 @@ private module ReturnNodes {
|
||||
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
|
||||
|
||||
/** Gets a syntactic return node that flows into this synthetic node. */
|
||||
ReturningNode getAnInput() {
|
||||
result.(NodeImpl).getCfgScope() = scope and
|
||||
result.getKind() = kind
|
||||
}
|
||||
pragma[nomagic]
|
||||
ReturningNode getAnInput() { result.hasKind(kind, scope) }
|
||||
|
||||
override ReturnKind getKind() { result = kind }
|
||||
|
||||
|
||||
@@ -166,28 +166,21 @@ module Public {
|
||||
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
|
||||
}
|
||||
|
||||
private predicate noComponentSpecific(SummaryComponent sc) {
|
||||
not exists(getComponentSpecific(sc))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this component used for flow summaries. */
|
||||
private string getComponent(SummaryComponent sc) {
|
||||
result = getComponentSpecific(sc)
|
||||
or
|
||||
noComponentSpecific(sc) and
|
||||
(
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this stack used for flow summaries. */
|
||||
@@ -335,7 +328,7 @@ module Public {
|
||||
class NeutralCallable extends SummarizedCallableBase {
|
||||
private Provenance provenance;
|
||||
|
||||
NeutralCallable() { neutralElement(this, provenance) }
|
||||
NeutralCallable() { neutralSummaryElement(this, provenance) }
|
||||
|
||||
/**
|
||||
* Holds if the neutral is auto generated.
|
||||
|
||||
@@ -62,10 +62,11 @@ predicate summaryElement(
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a neutral model exists for `c` with provenance `provenance`,
|
||||
* Holds if a neutral summary model exists for `c` with provenance `provenance`,
|
||||
* which means that there is no flow through `c`.
|
||||
* Note. Neutral models have not been implemented for Ruby.
|
||||
*/
|
||||
predicate neutralElement(FlowSummary::SummarizedCallable c, string provenance) { none() }
|
||||
predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string provenance) { none() }
|
||||
|
||||
bindingset[arg]
|
||||
private SummaryComponent interpretElementArg(string arg) {
|
||||
|
||||
@@ -112,6 +112,13 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryThroughStepTaint(
|
||||
DataFlow::Node arg, DataFlow::Node out, FlowSummaryImpl::Public::SummarizedCallable sc
|
||||
) {
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(arg, out, sc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
|
||||
* (intra-procedural) step.
|
||||
@@ -122,7 +129,7 @@ private module Cached {
|
||||
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Unicode transformation"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Unicode transformation"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module UnicodeBypassValidation {
|
||||
/**
|
||||
* A data flow source for "Unicode transformation" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "Unicode transformation" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "Unicode transformation" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.ApiGraphs
|
||||
import UnicodeBypassValidationCustomizations::UnicodeBypassValidation
|
||||
|
||||
/** A state signifying that a logical validation has not been performed. */
|
||||
class PreValidation extends DataFlow::FlowState {
|
||||
PreValidation() { this = "PreValidation" }
|
||||
}
|
||||
|
||||
/** A state signifying that a logical validation has been performed. */
|
||||
class PostValidation extends DataFlow::FlowState {
|
||||
PostValidation() { this = "PostValidation" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*
|
||||
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
|
||||
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnicodeBypassValidation" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
source instanceof RemoteFlowSource and state instanceof PreValidation
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(
|
||||
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
|
||||
DataFlow::FlowState stateTo
|
||||
) {
|
||||
(
|
||||
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
|
||||
or
|
||||
exists(RegexExecution re | nodeFrom = re.getString() and nodeTo = re)
|
||||
or
|
||||
// String Manipulation Method Calls
|
||||
// https://ruby-doc.org/core-2.7.0/String.html
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() =
|
||||
[
|
||||
[
|
||||
"ljust", "lstrip", "succ", "next", "rjust", "capitalize", "chomp", "gsub", "chop",
|
||||
"downcase", "swapcase", "uprcase", "scrub", "slice", "squeeze", "strip", "sub",
|
||||
"tr", "tr_s", "reverse"
|
||||
] + ["", "!"], "concat", "dump", "each_line", "replace", "insert", "inspect", "lines",
|
||||
"partition", "prepend", "replace", "rpartition", "scan", "split", "undump",
|
||||
"unpack" + ["", "1"]
|
||||
] and
|
||||
nodeFrom = cn.getReceiver() and
|
||||
nodeTo = cn
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() =
|
||||
[
|
||||
"casecmp" + ["", "?"], "center", "count", "each_char", "index", "rindex", "sum",
|
||||
["delete", "delete_prefix", "delete_suffix"] + ["", "!"],
|
||||
["start_with", "end_with" + "eql", "include"] + ["?", "!"], "match" + ["", "?"],
|
||||
] and
|
||||
nodeFrom = cn.getReceiver() and
|
||||
nodeTo = nodeFrom
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn = API::getTopLevelMember("CGI").getAMethodCall("escapeHTML") and
|
||||
nodeFrom = cn.getArgument(0) and
|
||||
nodeTo = cn
|
||||
)
|
||||
) and
|
||||
stateFrom instanceof PreValidation and
|
||||
stateTo instanceof PostValidation
|
||||
}
|
||||
|
||||
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
|
||||
(
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() = "unicode_normalize" and
|
||||
cn.getArgument(0).getConstantValue().getSymbol() = ["nfkc", "nfc", "nfkd", "nfd"] and
|
||||
sink = cn.getReceiver()
|
||||
)
|
||||
or
|
||||
// unicode_utils
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("UnicodeUtils").getMethod(["nfkd", "nfc", "nfd", "nfkc"]) and
|
||||
sink = mac.getParameter(0).asSink()
|
||||
)
|
||||
or
|
||||
// eprun
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("Eprun").getMethod("normalize") and
|
||||
sink = mac.getParameter(0).asSink()
|
||||
)
|
||||
or
|
||||
// unf
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("UNF").getMember("Normalizer").getMethod("normalize") and
|
||||
sink = mac.getParameter(0).asSink()
|
||||
)
|
||||
or
|
||||
// ActiveSupport::Multibyte::Chars
|
||||
exists(DataFlow::CallNode cn, DataFlow::CallNode n |
|
||||
cn =
|
||||
API::getTopLevelMember("ActiveSupport")
|
||||
.getMember("Multibyte")
|
||||
.getMember("Chars")
|
||||
.getMethod("new")
|
||||
.getCallNode() and
|
||||
n = cn.getAMethodCall("normalize") and
|
||||
sink = cn.getArgument(0)
|
||||
)
|
||||
) and
|
||||
state instanceof PostValidation
|
||||
}
|
||||
}
|
||||
77
ruby/ql/lib/codeql/ruby/frameworks/Pg.qll
Normal file
77
ruby/ql/lib/codeql/ruby/frameworks/Pg.qll
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Provides modeling for Pg, a Ruby library (gem) for interacting with PostgreSQL databases.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for Pg, a Ruby library (gem) for interacting with PostgreSQL databases.
|
||||
*/
|
||||
module Pg {
|
||||
/**
|
||||
* Flow summary for `PG.new()`. This method initializes a database connection.
|
||||
*/
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "PG.new()" }
|
||||
|
||||
override MethodCall getACall() { result = any(PgConnection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to PG::Connection.open() is used to establish a connection to a PostgreSQL database. */
|
||||
private class PgConnection extends DataFlow::CallNode {
|
||||
PgConnection() {
|
||||
this =
|
||||
API::getTopLevelMember("PG")
|
||||
.getMember("Connection")
|
||||
.getAMethodCall(["open", "new", "connect_start"])
|
||||
or
|
||||
this = API::getTopLevelMember("PG").getAnInstantiation()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that prepares an SQL statement to be executed later. */
|
||||
private class PgPrepareCall extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
private PgConnection pgConnection;
|
||||
private string queryName;
|
||||
|
||||
PgPrepareCall() {
|
||||
this = pgConnection.getAMethodCall("prepare") and
|
||||
queryName = this.getArgument(0).getConstantValue().getStringlikeValue() and
|
||||
query = this.getArgument(1)
|
||||
}
|
||||
|
||||
PgConnection getConnection() { result = pgConnection }
|
||||
|
||||
string getQueryName() { result = queryName }
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a PostgreSQL database. */
|
||||
private class PgExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
|
||||
PgExecution() {
|
||||
exists(PgConnection pgConnection |
|
||||
this =
|
||||
pgConnection.getAMethodCall(["exec", "async_exec", "exec_params", "async_exec_params"]) and
|
||||
query = this.getArgument(0)
|
||||
or
|
||||
exists(PgPrepareCall prepareCall |
|
||||
pgConnection = prepareCall.getConnection() and
|
||||
this.getArgument(0).getConstantValue().isStringlikeValue(prepareCall.getQueryName()) and
|
||||
query = prepareCall.getSql()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
}
|
||||
@@ -583,7 +583,8 @@ module Array {
|
||||
|
||||
private class DeleteUnknownSummary extends DeleteSummary {
|
||||
DeleteUnknownSummary() {
|
||||
this = "delete" and
|
||||
// Note: take care to avoid a name clash with the "delete" summary from String.qll
|
||||
this = "delete-unknown-key" and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
|
||||
}
|
||||
|
||||
|
||||
@@ -57,36 +57,40 @@ module Gem {
|
||||
}
|
||||
|
||||
/** Gets the name of the gem */
|
||||
string getName() { result = getSpecProperty("name").getConstantValue().getString() }
|
||||
string getName() { result = this.getSpecProperty("name").getConstantValue().getString() }
|
||||
|
||||
/** Gets a path that is loaded when the gem is required */
|
||||
private string getARequirePath() {
|
||||
result = getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()
|
||||
result =
|
||||
this.getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()
|
||||
or
|
||||
not exists(getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()) and
|
||||
not exists(
|
||||
this.getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()
|
||||
) and
|
||||
result = "lib" // the default is "lib"
|
||||
}
|
||||
|
||||
/** Gets a file that could be loaded when the gem is required. */
|
||||
private File getAPossiblyRequiredFile() {
|
||||
result = File.super.getParentContainer().getFolder(getARequirePath()).getAChildContainer*()
|
||||
result =
|
||||
File.super.getParentContainer().getFolder(this.getARequirePath()).getAChildContainer*()
|
||||
}
|
||||
|
||||
/** Gets a class/module that is exported by this gem. */
|
||||
private ModuleBase getAPublicModule() {
|
||||
result.(Toplevel).getLocation().getFile() = getAPossiblyRequiredFile()
|
||||
result.(Toplevel).getLocation().getFile() = this.getAPossiblyRequiredFile()
|
||||
or
|
||||
result = getAPublicModule().getAModule()
|
||||
result = this.getAPublicModule().getAModule()
|
||||
or
|
||||
result = getAPublicModule().getAClass()
|
||||
result = this.getAPublicModule().getAClass()
|
||||
or
|
||||
result = getAPublicModule().getStmt(_).(SingletonClass)
|
||||
result = this.getAPublicModule().getStmt(_).(SingletonClass)
|
||||
}
|
||||
|
||||
/** Gets a parameter from an exported method, which is an input to this gem. */
|
||||
DataFlow::ParameterNode getAnInputParameter() {
|
||||
exists(MethodBase method |
|
||||
method = getAPublicModule().getAMethod() and
|
||||
method = this.getAPublicModule().getAMethod() and
|
||||
result.getParameter() = method.getAParameter()
|
||||
|
|
||||
method.isPublic()
|
||||
|
||||
@@ -199,11 +199,13 @@ module Hash {
|
||||
}
|
||||
}
|
||||
|
||||
private class AssocUnknownSummary extends AssocSummary {
|
||||
AssocUnknownSummary() {
|
||||
this = "assoc" and
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
|
||||
private class AssocUnknownSummary extends SummarizedCallable {
|
||||
AssocUnknownSummary() { this = "assoc-unknown-arg" }
|
||||
|
||||
override MethodCall getACallSimple() {
|
||||
result.getMethodName() = "assoc" and
|
||||
result.getNumberOfArguments() = 1 and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(result.getArgument(0)))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
|
||||
@@ -21,6 +21,7 @@ private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.internal.TaintTrackingPrivate as TaintTrackingPrivate
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.frameworks.core.String
|
||||
|
||||
@@ -37,43 +38,6 @@ DataFlow::LocalSourceNode strStart() {
|
||||
/** Gets a dataflow node for a regular expression literal. */
|
||||
DataFlow::LocalSourceNode regStart() { result.asExpr().getExpr() instanceof Ast::RegExpLiteral }
|
||||
|
||||
/**
|
||||
* Holds if the analysis should track flow from `nodeFrom` to `nodeTo` on top of the ordinary type-tracking steps.
|
||||
* `nodeFrom` and `nodeTo` has type `fromType` and `toType` respectively.
|
||||
* The types are either "string" or "regexp".
|
||||
*/
|
||||
predicate step(
|
||||
DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo, string fromType, string toType
|
||||
) {
|
||||
fromType = toType and
|
||||
fromType = "string" and
|
||||
(
|
||||
// include taint flow through `String` summaries
|
||||
TaintTracking::localTaintStep(nodeFrom, nodeTo) and
|
||||
nodeFrom.(DataFlowPrivate::SummaryNode).getSummarizedCallable() instanceof
|
||||
String::SummarizedCallable
|
||||
or
|
||||
// string concatenations, and
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
|
||||
)
|
||||
or
|
||||
// string interpolations
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
)
|
||||
or
|
||||
fromType = "string" and
|
||||
toType = "reg" and
|
||||
exists(DataFlow::CallNode call |
|
||||
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
|
||||
nodeFrom = call.getArgument(0) and
|
||||
nodeTo = call
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node where string values that flow to the node are interpreted as regular expressions. */
|
||||
DataFlow::Node stringSink() {
|
||||
result instanceof RE::RegExpInterpretation::Range and
|
||||
@@ -91,28 +55,139 @@ DataFlow::Node stringSink() {
|
||||
/** Gets a node where regular expressions that flow to the node are used. */
|
||||
DataFlow::Node regSink() { result = any(RegexExecution exec).getRegex() }
|
||||
|
||||
/** Gets a node that is reachable by type-tracking from any string or regular expression. */
|
||||
DataFlow::LocalSourceNode forward(TypeTracker t) {
|
||||
t.start() and
|
||||
result = [strStart(), regStart()]
|
||||
or
|
||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
||||
or
|
||||
exists(TypeTracker t2 | t2 = t.continue() | step(forward(t2).getALocalUse(), result, _, _))
|
||||
private signature module TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start);
|
||||
|
||||
predicate end(DataFlow::Node n);
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is backwards reachable from any regular expression use,
|
||||
* where that use is reachable by type-tracking from any string or regular expression.
|
||||
* Provides a version of type tracking where we first prune for reachable nodes,
|
||||
* before doing the type tracking computation.
|
||||
*/
|
||||
DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
t.start() and
|
||||
result.flowsTo([stringSink(), regSink()]) and
|
||||
result = forward(TypeTracker::end())
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
||||
or
|
||||
exists(TypeBackTracker t2 | t2 = t.continue() | step(result.getALocalUse(), backwards(t2), _, _))
|
||||
private module TypeTrack<TypeTrackInputSig Input> {
|
||||
private predicate additionalStep(
|
||||
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
Input::additionalStep(nodeFrom.getALocalUse(), nodeTo)
|
||||
}
|
||||
|
||||
/** Gets a node that is forwards reachable by type-tracking. */
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode forward(TypeTracker t) {
|
||||
result = Input::start(t, _)
|
||||
or
|
||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
||||
or
|
||||
exists(TypeTracker t2 | t2 = t.continue() | additionalStep(forward(t2), result))
|
||||
}
|
||||
|
||||
bindingset[result, tbt]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private DataFlow::LocalSourceNode forwardLateInline(TypeBackTracker tbt) {
|
||||
exists(TypeTracker tt |
|
||||
result = forward(tt) and
|
||||
tt = tbt.getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that is backwards reachable by type-tracking. */
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
result = forwardLateInline(t) and
|
||||
(
|
||||
t.start() and
|
||||
Input::end(result.getALocalUse())
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
||||
or
|
||||
exists(TypeBackTracker t2 | t2 = t.continue() | additionalStep(result, backwards(t2)))
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[result, tt]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private DataFlow::LocalSourceNode backwardsInlineLate(TypeTracker tt) {
|
||||
exists(TypeBackTracker tbt |
|
||||
result = backwards(tbt) and
|
||||
tt = tbt.getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is forwards and backwards reachable with type tracker `t`. */
|
||||
pragma[nomagic]
|
||||
private predicate reached(DataFlow::LocalSourceNode n, TypeTracker t) {
|
||||
n = forward(t) and
|
||||
n = backwardsInlineLate(t)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTracker stepReached(
|
||||
TypeTracker t, DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, nodeTo, summary) and
|
||||
reached(nodeFrom, t) and
|
||||
reached(nodeTo, result) and
|
||||
result = t.append(summary)
|
||||
)
|
||||
or
|
||||
additionalStep(nodeFrom, nodeTo) and
|
||||
reached(nodeFrom, pragma[only_bind_into](t)) and
|
||||
reached(nodeTo, pragma[only_bind_into](t)) and
|
||||
result = t.continue()
|
||||
}
|
||||
|
||||
/** Gets a node that has been tracked from the start node `start`. */
|
||||
DataFlow::LocalSourceNode track(DataFlow::Node start, TypeTracker t) {
|
||||
t.start() and
|
||||
result = Input::start(t, start) and
|
||||
reached(result, t)
|
||||
or
|
||||
exists(TypeTracker t2 | t = stepReached(t2, track(start, t2), result))
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `inputStr` is compiled to a regular expression that is returned at `call`. */
|
||||
pragma[nomagic]
|
||||
private predicate regFromString(DataFlow::LocalSourceNode inputStr, DataFlow::CallNode call) {
|
||||
exists(DataFlow::Node mid |
|
||||
inputStr.flowsTo(mid) and
|
||||
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
|
||||
mid = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
private module StringTypeTrackInput implements TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
|
||||
start = strStart() and t.start() and result = start
|
||||
}
|
||||
|
||||
predicate end(DataFlow::Node n) {
|
||||
n = stringSink() or
|
||||
regFromString(n, _)
|
||||
}
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) {
|
||||
// include taint flow through `String` summaries
|
||||
TaintTrackingPrivate::summaryThroughStepTaint(nodeFrom, nodeTo,
|
||||
any(String::SummarizedCallable c))
|
||||
or
|
||||
// string concatenations, and
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
|
||||
)
|
||||
or
|
||||
// string interpolations
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,41 +195,34 @@ DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
* This is used to figure out where `start` is evaluated as a regular expression against an input string,
|
||||
* or where `start` is compiled into a regular expression.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackStrings(DataFlow::Node start, TypeTracker t) {
|
||||
result = backwards(_) and
|
||||
(
|
||||
private predicate trackStrings = TypeTrack<StringTypeTrackInput>::track/2;
|
||||
|
||||
/** Holds if `strConst` flows to a regex compilation (tracked by `t`), where the resulting regular expression is stored in `reg`. */
|
||||
pragma[nomagic]
|
||||
private predicate regFromStringStart(DataFlow::Node strConst, TypeTracker t, DataFlow::CallNode reg) {
|
||||
regFromString(trackStrings(strConst, t), reg) and
|
||||
exists(t.continue())
|
||||
}
|
||||
|
||||
private module RegTypeTrackInput implements TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
|
||||
start = regStart() and
|
||||
t.start() and
|
||||
start = result and
|
||||
result = strStart()
|
||||
result = start
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackStrings(start, t2).track(t2, t))
|
||||
or
|
||||
// an additional step from string to string
|
||||
exists(TypeTracker t2 | t2 = t.continue() |
|
||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "string")
|
||||
)
|
||||
)
|
||||
regFromStringStart(start, t, result)
|
||||
}
|
||||
|
||||
predicate end(DataFlow::Node n) { n = regSink() }
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that has been tracked from the regular expression `start` to some node.
|
||||
* This is used to figure out where `start` is executed against an input string.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackRegs(DataFlow::Node start, TypeTracker t) {
|
||||
result = backwards(_) and
|
||||
(
|
||||
t.start() and
|
||||
start = result and
|
||||
result = regStart()
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackRegs(start, t2).track(t2, t))
|
||||
or
|
||||
// an additional step where a string is converted to a regular expression
|
||||
exists(TypeTracker t2 | t2 = t.continue() |
|
||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "reg")
|
||||
)
|
||||
)
|
||||
}
|
||||
private predicate trackRegs = TypeTrack<RegTypeTrackInput>::track/2;
|
||||
|
||||
/** Gets a node that references a regular expression. */
|
||||
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {
|
||||
|
||||
@@ -613,8 +613,17 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
* also flow to `sink`.
|
||||
*/
|
||||
TypeTracker getACompatibleTypeTracker() {
|
||||
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
|
||||
hasCall = false or this.hasReturn() = false
|
||||
exists(boolean hasCall, OptionalTypeTrackerContent c |
|
||||
result = MkTypeTracker(hasCall, c) and
|
||||
(
|
||||
compatibleContents(c, content)
|
||||
or
|
||||
content = noContent() and c = content
|
||||
)
|
||||
|
|
||||
hasCall = false
|
||||
or
|
||||
this.hasReturn() = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +80,8 @@ predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
pragma[nomagic]
|
||||
private predicate flowThrough(DataFlowPublic::ParameterNode param) {
|
||||
exists(DataFlowPrivate::ReturningNode returnNode, DataFlowDispatch::ReturnKind rk |
|
||||
DataFlowPrivate::LocalFlow::getParameterDefNode(param.getParameter())
|
||||
.(TypeTrackingNode)
|
||||
.flowsTo(returnNode) and
|
||||
rk = returnNode.getKind()
|
||||
param.flowsTo(returnNode) and
|
||||
returnNode.hasKind(rk, param.(DataFlowPrivate::NodeImpl).getCfgScope())
|
||||
|
|
||||
rk instanceof DataFlowDispatch::NormalReturnKind
|
||||
or
|
||||
@@ -91,12 +89,23 @@ private predicate flowThrough(DataFlowPublic::ParameterNode param) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if there is flow from `arg` to `p` via the call `call`, not counting `new -> initialize` call steps. */
|
||||
pragma[nomagic]
|
||||
predicate callStepNoInitialize(
|
||||
ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::ParameterNodeImpl p
|
||||
) {
|
||||
exists(DataFlowDispatch::ParameterPosition pos |
|
||||
argumentPositionMatch(call, arg, pos) and
|
||||
p.isSourceParameterOf(DataFlowDispatch::getTarget(call), pos)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
|
||||
pragma[nomagic]
|
||||
predicate levelStepCall(Node nodeFrom, Node nodeTo) {
|
||||
exists(DataFlowPublic::ParameterNode param |
|
||||
flowThrough(param) and
|
||||
callStep(nodeTo.asExpr(), nodeFrom, param)
|
||||
callStepNoInitialize(nodeTo.asExpr(), nodeFrom, param)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -600,14 +609,26 @@ private DataFlow::Node evaluateSummaryComponentStackLocal(
|
||||
pragma[only_bind_out](tail)) and
|
||||
stack = SCS::push(pragma[only_bind_out](head), pragma[only_bind_out](tail))
|
||||
|
|
||||
exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos |
|
||||
exists(
|
||||
DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos,
|
||||
DataFlowPrivate::ParameterNodeImpl p
|
||||
|
|
||||
head = SummaryComponent::parameter(apos) and
|
||||
DataFlowDispatch::parameterMatch(ppos, apos) and
|
||||
result.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(prev.asExpr().getExpr(), ppos)
|
||||
p.isSourceParameterOf(prev.asExpr().getExpr(), ppos) and
|
||||
// We need to include both `p` and the SSA definition for `p`, since in type-tracking
|
||||
// the step from `p` to the SSA definition is considered a call step.
|
||||
result =
|
||||
[p.(DataFlow::Node), DataFlowPrivate::LocalFlow::getParameterDefNode(p.getParameter())]
|
||||
)
|
||||
or
|
||||
head = SummaryComponent::return() and
|
||||
result.(DataFlowPrivate::SynthReturnNode).getCfgScope() = prev.asExpr().getExpr()
|
||||
exists(DataFlowPrivate::SynthReturnNode ret |
|
||||
head = SummaryComponent::return() and
|
||||
ret.getCfgScope() = prev.asExpr().getExpr() and
|
||||
// We need to include both `ret` and `ret.getAnInput()`, since in type-tracking
|
||||
// the step from `ret.getAnInput()` to `ret` is considered a return step.
|
||||
result = [ret.(DataFlow::Node), ret.getAnInput()]
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ContentSet content |
|
||||
head = SummaryComponent::withoutContent(content) and
|
||||
|
||||
22
ruby/ql/lib/ide-contextual-queries/printCfg.ql
Normal file
22
ruby/ql/lib/ide-contextual-queries/printCfg.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Print CFG
|
||||
* @description Produces a representation of a file's Control Flow Graph.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id rb/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::TestOutput
|
||||
private import codeql.IDEContextual
|
||||
|
||||
/**
|
||||
* Gets the source file to generate a CFG from.
|
||||
*/
|
||||
external string selectedSourceFile();
|
||||
|
||||
class MyRelevantNode extends RelevantNode {
|
||||
MyRelevantNode() {
|
||||
this.getScope().getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.6.2-dev
|
||||
version: 0.6.3-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
@@ -12,3 +12,4 @@ dependencies:
|
||||
codeql/util: ${workspace}
|
||||
dataExtensions:
|
||||
- codeql/ruby/frameworks/**/model.yml
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
// CodeQL database schema for Ruby
|
||||
// Automatically generated from the tree-sitter grammar; do not edit
|
||||
|
||||
@location = @location_default
|
||||
/*- Files and folders -*/
|
||||
|
||||
/**
|
||||
* The location of an element.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `file`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
locations_default(
|
||||
unique int id: @location_default,
|
||||
int file: @file ref,
|
||||
int start_line: int ref,
|
||||
int start_column: int ref,
|
||||
int end_line: int ref,
|
||||
int end_column: int ref
|
||||
int beginLine: int ref,
|
||||
int beginColumn: int ref,
|
||||
int endLine: int ref,
|
||||
int endColumn: int ref
|
||||
);
|
||||
|
||||
files(
|
||||
@@ -29,9 +36,14 @@ containerparent(
|
||||
unique int child: @container ref
|
||||
);
|
||||
|
||||
sourceLocationPrefix(
|
||||
string prefix: string ref
|
||||
);
|
||||
/*- Source location prefix -*/
|
||||
|
||||
/**
|
||||
* The source location of the snapshot.
|
||||
*/
|
||||
sourceLocationPrefix(string prefix : string ref);
|
||||
|
||||
/*- Diagnostic messages -*/
|
||||
|
||||
diagnostics(
|
||||
unique int id: @diagnostic,
|
||||
@@ -42,6 +54,8 @@ diagnostics(
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
/*- Diagnostic messages: severity -*/
|
||||
|
||||
case @diagnostic.severity of
|
||||
10 = @diagnostic_debug
|
||||
| 20 = @diagnostic_info
|
||||
@@ -49,7 +63,46 @@ case @diagnostic.severity of
|
||||
| 40 = @diagnostic_error
|
||||
;
|
||||
|
||||
/*- YAML -*/
|
||||
|
||||
#keyset[parent, idx]
|
||||
yaml (unique int id: @yaml_node,
|
||||
int kind: int ref,
|
||||
int parent: @yaml_node_parent ref,
|
||||
int idx: int ref,
|
||||
string tag: string ref,
|
||||
string tostring: string ref);
|
||||
|
||||
case @yaml_node.kind of
|
||||
0 = @yaml_scalar_node
|
||||
| 1 = @yaml_mapping_node
|
||||
| 2 = @yaml_sequence_node
|
||||
| 3 = @yaml_alias_node
|
||||
;
|
||||
|
||||
@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node;
|
||||
|
||||
@yaml_node_parent = @yaml_collection_node | @file;
|
||||
|
||||
yaml_anchors (unique int node: @yaml_node ref,
|
||||
string anchor: string ref);
|
||||
|
||||
yaml_aliases (unique int alias: @yaml_alias_node ref,
|
||||
string target: string ref);
|
||||
|
||||
yaml_scalars (unique int scalar: @yaml_scalar_node ref,
|
||||
int style: int ref,
|
||||
string value: string ref);
|
||||
|
||||
yaml_errors (unique int id: @yaml_error,
|
||||
string message: string ref);
|
||||
|
||||
yaml_locations(unique int locatable: @yaml_locatable ref,
|
||||
int location: @location_default ref);
|
||||
|
||||
@yaml_locatable = @yaml_node | @yaml_error;
|
||||
|
||||
/*- Ruby dbscheme -*/
|
||||
@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary
|
||||
|
||||
@ruby_underscore_call_operator = @ruby_reserved_word
|
||||
@@ -1375,9 +1428,10 @@ ruby_ast_node_info(
|
||||
unique int node: @ruby_ast_node ref,
|
||||
int parent: @ruby_ast_node_parent ref,
|
||||
int parent_index: int ref,
|
||||
int loc: @location ref
|
||||
int loc: @location_default ref
|
||||
);
|
||||
|
||||
/*- Erb dbscheme -*/
|
||||
erb_comment_directive_child(
|
||||
unique int erb_comment_directive: @erb_comment_directive ref,
|
||||
unique int child: @erb_token_comment ref
|
||||
@@ -1450,6 +1504,6 @@ erb_ast_node_info(
|
||||
unique int node: @erb_ast_node ref,
|
||||
int parent: @erb_ast_node_parent ref,
|
||||
int parent_index: int ref,
|
||||
int loc: @location ref
|
||||
int loc: @location_default ref
|
||||
);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Sync dbscheme fragments
|
||||
compatibility: full
|
||||
Reference in New Issue
Block a user