mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge remote-tracking branch 'origin/main' into rb/sensitive-get-query
This commit is contained in:
@@ -216,7 +216,7 @@ struct Visitor<'a> {
|
||||
schema: &'a NodeTypeMap,
|
||||
/// A stack for gathering information from child nodes. Whenever a node is
|
||||
/// entered the parent's [Label], child counter, and an empty list is pushed.
|
||||
/// All children append their data to the the list. When the visitor leaves a
|
||||
/// All children append their data to the list. When the visitor leaves a
|
||||
/// node the list containing the child data is popped from the stack and
|
||||
/// matched against the dbscheme for the node. If the expectations are met
|
||||
/// the corresponding row definitions are added to the trap_output.
|
||||
|
||||
@@ -68,7 +68,6 @@ fn main() -> std::io::Result<()> {
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("ruby_extractor=warn")),
|
||||
)
|
||||
.init();
|
||||
tracing::warn!("Support for Ruby is currently in Beta: https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/");
|
||||
let num_threads = num_codeql_threads();
|
||||
tracing::info!(
|
||||
"Using {} {}",
|
||||
|
||||
@@ -43,7 +43,7 @@ pub enum FieldTypeInfo {
|
||||
},
|
||||
|
||||
/// The field can be one of several tokens, so the db type will be an `int`
|
||||
/// with a `case @foo.kind` for each possiblity.
|
||||
/// with a `case @foo.kind` for each possibility.
|
||||
ReservedWordInt(BTreeMap<String, (usize, String)>),
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Calls to `params` in `ActionMailer` classes are now treated as sources of remote user input.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* `ActiveJob::Serializers.deserialize` is considered to be a code execution sink.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* More sources of remote input arising from methods on `ActionDispatch::Request`
|
||||
are now recognised.
|
||||
@@ -522,7 +522,8 @@ module API {
|
||||
or
|
||||
exists(TypeTrackerSpecific::TypeTrackerContent c |
|
||||
TypeTrackerSpecific::basicLoadStep(node, ref, c) and
|
||||
lbl = Label::content(c.getAStoreContent())
|
||||
lbl = Label::content(c.getAStoreContent()) and
|
||||
not c.isSingleton(any(DataFlow::Content::AttributeNameContent k))
|
||||
)
|
||||
// note: method calls are not handled here as there is no DataFlow::Node for the intermediate MkMethodAccessNode API node
|
||||
}
|
||||
@@ -638,7 +639,10 @@ module API {
|
||||
isUse(src) and
|
||||
t.start()
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
|
||||
exists(TypeTracker t2 |
|
||||
result = trackUseNode(src, t2).track(t2, t) and
|
||||
not result instanceof DataFlowPrivate::SelfParameterNode
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -657,7 +661,11 @@ module API {
|
||||
isDef(rhs) and
|
||||
result = rhs.getALocalSource()
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = trackDefNode(rhs, t2).backtrack(t2, t))
|
||||
exists(TypeBackTracker t2, DataFlow::LocalSourceNode mid |
|
||||
mid = trackDefNode(rhs, t2) and
|
||||
not mid instanceof DataFlowPrivate::SelfParameterNode and
|
||||
result = mid.backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a data flow node reaching the RHS of the given def node. */
|
||||
@@ -890,7 +898,7 @@ module API {
|
||||
/** Gets the `subclass` edge label. */
|
||||
LabelSubclass subclass() { any() }
|
||||
|
||||
/** Gets the label representing the given keword argument/parameter. */
|
||||
/** Gets the label representing the given keyword argument/parameter. */
|
||||
LabelKeywordParameter keywordParameter(string name) { result.getName() = name }
|
||||
|
||||
/** Gets the label representing the `n`th positional argument/parameter. */
|
||||
|
||||
@@ -300,6 +300,26 @@ module Http {
|
||||
}
|
||||
}
|
||||
|
||||
/** A kind of request input. */
|
||||
class RequestInputKind extends string {
|
||||
RequestInputKind() { this = ["parameter", "header", "body", "url", "cookie"] }
|
||||
}
|
||||
|
||||
/** Input from the parameters of a request. */
|
||||
RequestInputKind parameterInputKind() { result = "parameter" }
|
||||
|
||||
/** Input from the headers of a request. */
|
||||
RequestInputKind headerInputKind() { result = "header" }
|
||||
|
||||
/** Input from the body of a request. */
|
||||
RequestInputKind bodyInputKind() { result = "body" }
|
||||
|
||||
/** Input from the URL of a request. */
|
||||
RequestInputKind urlInputKind() { result = "url" }
|
||||
|
||||
/** Input from the cookies of a request. */
|
||||
RequestInputKind cookieInputKind() { result = "cookie" }
|
||||
|
||||
/**
|
||||
* An access to a user-controlled HTTP request input. For example, the URL or body of a request.
|
||||
* Instances of this class automatically become `RemoteFlowSource`s.
|
||||
@@ -318,10 +338,28 @@ module Http {
|
||||
/**
|
||||
* Gets the kind of the accessed input,
|
||||
* Can be one of "parameter", "header", "body", "url", "cookie".
|
||||
*
|
||||
* Note that this predicate is functional.
|
||||
*/
|
||||
string getKind() { result = super.getKind() }
|
||||
RequestInputKind getKind() { result = super.getKind() }
|
||||
|
||||
/**
|
||||
* Holds if this part of the request may be controlled by a third party,
|
||||
* that is, an agent other than the one who sent the request.
|
||||
*
|
||||
* This is true for the URL, query parameters, and request body.
|
||||
* These can be controlled by a malicious third party in the following scenarios:
|
||||
*
|
||||
* - The user clicks a malicious link or is otherwise redirected to a malicious URL.
|
||||
* - The user visits a web site that initiates a form submission or AJAX request on their behalf.
|
||||
*
|
||||
* In these cases, the request is technically sent from the user's browser, but
|
||||
* the user is not in direct control of the URL or POST body.
|
||||
*
|
||||
* Headers are never considered third-party controllable by this predicate, although the
|
||||
* third party does have some control over the the Referer and Origin headers.
|
||||
*/
|
||||
predicate isThirdPartyControllable() {
|
||||
this.getKind() = [parameterInputKind(), urlInputKind(), bodyInputKind()]
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new HTTP request inputs. */
|
||||
@@ -343,10 +381,7 @@ module Http {
|
||||
/**
|
||||
* Gets the kind of the accessed input,
|
||||
* Can be one of "parameter", "header", "body", "url", "cookie".
|
||||
*
|
||||
* Note that this predicate is functional.
|
||||
*/
|
||||
abstract string getKind();
|
||||
abstract RequestInputKind getKind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +463,7 @@ module Http {
|
||||
|
||||
override string getSourceType() { result = handler.getFramework() + " RoutedParameter" }
|
||||
|
||||
override string getKind() { result = "url" }
|
||||
override RequestInputKind getKind() { result = parameterInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
private import codeql.ruby.frameworks.Core
|
||||
private import codeql.ruby.frameworks.ActionCable
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveJob
|
||||
private import codeql.ruby.frameworks.ActionMailer
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveResource
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
|
||||
@@ -106,7 +106,7 @@ class MethodCall extends Call instanceof MethodCallImpl {
|
||||
final Block getBlock() { result = super.getBlockImpl() }
|
||||
|
||||
/**
|
||||
* Holds if the safe nagivation operator (`&.`) is used in this call.
|
||||
* Holds if the safe navigation operator (`&.`) is used in this call.
|
||||
* ```rb
|
||||
* foo&.empty?
|
||||
* ```
|
||||
|
||||
@@ -65,7 +65,7 @@ class ConstantValue extends TConstantValue {
|
||||
/** Holds if this is the string value `s`. */
|
||||
predicate isString(string s) { s = this.getString() }
|
||||
|
||||
/** Gets the symbol value (exluding the `:` prefix), if this is a symbol. */
|
||||
/** Gets the symbol value (excluding the `:` prefix), if this is a symbol. */
|
||||
string getSymbol() { this = TSymbol(result) }
|
||||
|
||||
/** Holds if this is the symbol value `:s`. */
|
||||
|
||||
@@ -394,7 +394,7 @@ private module ResolveImpl {
|
||||
|
||||
/**
|
||||
* The qualified names of the ancestors of a class/module. The ancestors should be an ordered list
|
||||
* of the ancestores of `prepend`ed modules, the module itself , the ancestors or `include`d modules
|
||||
* of the ancestors of `prepend`ed modules, the module itself , the ancestors or `include`d modules
|
||||
* and the ancestors of the super class. The priority value only distinguishes the kind of ancestor,
|
||||
* it does not order the ancestors within a group of the same kind. This is an over-approximation, however,
|
||||
* computing the precise order is tricky because it depends on the evaluation/file loading order.
|
||||
@@ -486,12 +486,15 @@ private import ResolveImpl
|
||||
* methods evaluate the block in the context of some other module/class instead of
|
||||
* the enclosing one.
|
||||
*/
|
||||
private ModuleBase enclosingModule(AstNode node) { result = parent*(node).getParent() }
|
||||
|
||||
private AstNode parent(AstNode n) {
|
||||
result = n.getParent() and
|
||||
not result instanceof ModuleBase and
|
||||
not result instanceof Block
|
||||
private ModuleBase enclosingModule(AstNode node) {
|
||||
result = node.getParent()
|
||||
or
|
||||
exists(AstNode mid |
|
||||
result = enclosingModule(mid) and
|
||||
mid = node.getParent() and
|
||||
not mid instanceof ModuleBase and
|
||||
not mid instanceof Block
|
||||
)
|
||||
}
|
||||
|
||||
private Module getAncestors(Module m) {
|
||||
|
||||
@@ -118,7 +118,11 @@ class Synthesis extends TSynthesis {
|
||||
private class Desugared extends AstNode {
|
||||
Desugared() { this = any(AstNode sugar).getDesugared() }
|
||||
|
||||
AstNode getADescendant() { result = this.getAChild*() }
|
||||
AstNode getADescendant() {
|
||||
result = this
|
||||
or
|
||||
result = this.getADescendant().getAChild()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +136,10 @@ int desugarLevel(AstNode n) { result = count(Desugared desugared | n = desugared
|
||||
* Holds if `n` appears in a context that is desugared. That is, a
|
||||
* transitive, reflexive parent of `n` is a desugared node.
|
||||
*/
|
||||
predicate isInDesugaredContext(AstNode n) { n = any(AstNode sugar).getDesugared().getAChild*() }
|
||||
predicate isInDesugaredContext(AstNode n) {
|
||||
n = any(AstNode sugar).getDesugared() or
|
||||
n = any(AstNode mid | isInDesugaredContext(mid)).getAChild()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a node that only exists as a result of desugaring some
|
||||
|
||||
@@ -71,18 +71,22 @@ private predicate completionIsValidForStmt(AstNode n, Completion c) {
|
||||
c = TReturnCompletion()
|
||||
}
|
||||
|
||||
private AstNode getARescuableBodyChild() {
|
||||
exists(Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) |
|
||||
exists(bst.getARescue())
|
||||
or
|
||||
exists(bst.getEnsure())
|
||||
)
|
||||
or
|
||||
result = getARescuableBodyChild().getAChild()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` happens in an exception-aware context, that is, it may be
|
||||
* `rescue`d or `ensure`d. In such cases, we assume that the target of `c`
|
||||
* may raise an exception (in addition to evaluating normally).
|
||||
*/
|
||||
private predicate mayRaise(Call c) {
|
||||
exists(Trees::BodyStmtTree bst | c = bst.getBodyChild(_, true).getAChild*() |
|
||||
exists(bst.getARescue())
|
||||
or
|
||||
exists(bst.getEnsure())
|
||||
)
|
||||
}
|
||||
private predicate mayRaise(Call c) { c = getARescuableBodyChild() }
|
||||
|
||||
/** A completion of a statement or an expression. */
|
||||
abstract class Completion extends TCompletion {
|
||||
|
||||
@@ -885,7 +885,7 @@ module TestOutput {
|
||||
/**
|
||||
* Gets a string used to resolve ties in node and edge ordering.
|
||||
*/
|
||||
string getOrderDisambuigation() { result = "" }
|
||||
string getOrderDisambiguation() { result = "" }
|
||||
}
|
||||
|
||||
query predicate nodes(RelevantNode n, string attr, string val) {
|
||||
@@ -900,7 +900,7 @@ module TestOutput {
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString(),
|
||||
p.getOrderDisambuigation()
|
||||
p.getOrderDisambiguation()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
@@ -923,7 +923,7 @@ module TestOutput {
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString(), s.toString(),
|
||||
s.getOrderDisambuigation()
|
||||
s.getOrderDisambiguation()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowDispatch
|
||||
private import internal.DataFlowPrivate
|
||||
@@ -11,6 +9,7 @@ private import internal.DataFlowPrivate
|
||||
// import all instances below
|
||||
private module Summaries {
|
||||
private import codeql.ruby.Frameworks
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
}
|
||||
|
||||
class SummaryComponent = Impl::Public::SummaryComponent;
|
||||
@@ -47,7 +46,7 @@ module SummaryComponent {
|
||||
|
||||
/**
|
||||
* Gets a summary component that represents an element in a collection at a specific
|
||||
* known index `cv`, or an uknown index.
|
||||
* known index `cv`, or an unknown index.
|
||||
*/
|
||||
SummaryComponent elementKnownOrUnknown(ConstantValue cv) {
|
||||
result = SC::content(TKnownOrUnknownElementContent(TKnownElementContent(cv)))
|
||||
@@ -144,33 +143,3 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
|
||||
}
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
|
||||
private class SummarizedCallableFromModel extends SummarizedCallable {
|
||||
string package;
|
||||
string type;
|
||||
string path;
|
||||
|
||||
SummarizedCallableFromModel() {
|
||||
ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
|
||||
this = package + ";" + type + ";" + path
|
||||
}
|
||||
|
||||
override Call getACall() {
|
||||
exists(API::MethodAccessNode base |
|
||||
ModelOutput::resolvedSummaryBase(package, type, path, base) and
|
||||
result = base.getCallNode().asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
exists(string kind |
|
||||
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
||||
|
|
||||
kind = "value" and
|
||||
preservesValue = true
|
||||
or
|
||||
kind = "taint" and
|
||||
preservesValue = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides an extension point for for modeling user-controlled data.
|
||||
* Provides an extension point for modeling user-controlled data.
|
||||
* Such data is often used as data-flow sources in security queries.
|
||||
*/
|
||||
|
||||
|
||||
@@ -163,7 +163,9 @@ abstract class Configuration extends string {
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
|
||||
predicate hasFlowTo(Node sink) {
|
||||
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
|
||||
* by `revFlow`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
revFlowConsCand(c, conf) and
|
||||
revFlowStore(c, _, _, conf)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableReturnPosOutNodeCandFwd1(
|
||||
additional predicate viableReturnPosOutNodeCandFwd1(
|
||||
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
||||
) {
|
||||
fwdFlowReturnPosition(pos, _, config) and
|
||||
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableParamArgNodeCandFwd1(
|
||||
additional predicate viableParamArgNodeCandFwd1(
|
||||
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
||||
) {
|
||||
viableParamArgEx(call, p, arg) and
|
||||
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlowState(FlowState state, Configuration config) {
|
||||
additional predicate revFlowState(FlowState state, Configuration config) {
|
||||
exists(NodeEx node |
|
||||
sinkNode(node, state, config) and
|
||||
revFlow(node, _, pragma[only_bind_into](config)) and
|
||||
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* argument.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate fwdFlow(
|
||||
additional predicate fwdFlow(
|
||||
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlow0(node, state, cc, argAp, ap, config) and
|
||||
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* the access path of the returned value.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlow(
|
||||
additional predicate revFlow(
|
||||
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlow0(node, state, toReturn, returnAp, ap, config) and
|
||||
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
revFlow(node, state, _, _, _, config)
|
||||
}
|
||||
|
||||
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
|
||||
additional predicate revFlowAlias(NodeEx node, Configuration config) {
|
||||
revFlow(node, _, _, _, _, config)
|
||||
}
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
revFlow(node, state, ap, config)
|
||||
}
|
||||
|
||||
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
revConsCand(tc, ap, config) and
|
||||
validAp(ap, config)
|
||||
}
|
||||
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
pragma[nomagic]
|
||||
private PathNodeImpl getANonHiddenSuccessor0() {
|
||||
result = this.getASuccessorIfHidden*() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
|
||||
not this.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
|
||||
@@ -163,7 +163,9 @@ abstract class Configuration extends string {
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
|
||||
predicate hasFlowTo(Node sink) {
|
||||
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
|
||||
* by `revFlow`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
revFlowConsCand(c, conf) and
|
||||
revFlowStore(c, _, _, conf)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableReturnPosOutNodeCandFwd1(
|
||||
additional predicate viableReturnPosOutNodeCandFwd1(
|
||||
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
||||
) {
|
||||
fwdFlowReturnPosition(pos, _, config) and
|
||||
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableParamArgNodeCandFwd1(
|
||||
additional predicate viableParamArgNodeCandFwd1(
|
||||
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
||||
) {
|
||||
viableParamArgEx(call, p, arg) and
|
||||
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlowState(FlowState state, Configuration config) {
|
||||
additional predicate revFlowState(FlowState state, Configuration config) {
|
||||
exists(NodeEx node |
|
||||
sinkNode(node, state, config) and
|
||||
revFlow(node, _, pragma[only_bind_into](config)) and
|
||||
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* argument.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate fwdFlow(
|
||||
additional predicate fwdFlow(
|
||||
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlow0(node, state, cc, argAp, ap, config) and
|
||||
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* the access path of the returned value.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlow(
|
||||
additional predicate revFlow(
|
||||
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlow0(node, state, toReturn, returnAp, ap, config) and
|
||||
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
revFlow(node, state, _, _, _, config)
|
||||
}
|
||||
|
||||
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
|
||||
additional predicate revFlowAlias(NodeEx node, Configuration config) {
|
||||
revFlow(node, _, _, _, _, config)
|
||||
}
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
revFlow(node, state, ap, config)
|
||||
}
|
||||
|
||||
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
revConsCand(tc, ap, config) and
|
||||
validAp(ap, config)
|
||||
}
|
||||
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
pragma[nomagic]
|
||||
private PathNodeImpl getANonHiddenSuccessor0() {
|
||||
result = this.getASuccessorIfHidden*() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
|
||||
not this.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
|
||||
@@ -163,7 +163,9 @@ abstract class Configuration extends string {
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
|
||||
predicate hasFlowTo(Node sink) {
|
||||
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
|
||||
* by `revFlow`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
revFlowConsCand(c, conf) and
|
||||
revFlowStore(c, _, _, conf)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableReturnPosOutNodeCandFwd1(
|
||||
additional predicate viableReturnPosOutNodeCandFwd1(
|
||||
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
||||
) {
|
||||
fwdFlowReturnPosition(pos, _, config) and
|
||||
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableParamArgNodeCandFwd1(
|
||||
additional predicate viableParamArgNodeCandFwd1(
|
||||
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
||||
) {
|
||||
viableParamArgEx(call, p, arg) and
|
||||
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlowState(FlowState state, Configuration config) {
|
||||
additional predicate revFlowState(FlowState state, Configuration config) {
|
||||
exists(NodeEx node |
|
||||
sinkNode(node, state, config) and
|
||||
revFlow(node, _, pragma[only_bind_into](config)) and
|
||||
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* argument.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate fwdFlow(
|
||||
additional predicate fwdFlow(
|
||||
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlow0(node, state, cc, argAp, ap, config) and
|
||||
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* the access path of the returned value.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlow(
|
||||
additional predicate revFlow(
|
||||
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlow0(node, state, toReturn, returnAp, ap, config) and
|
||||
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
revFlow(node, state, _, _, _, config)
|
||||
}
|
||||
|
||||
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
|
||||
additional predicate revFlowAlias(NodeEx node, Configuration config) {
|
||||
revFlow(node, _, _, _, _, config)
|
||||
}
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
revFlow(node, state, ap, config)
|
||||
}
|
||||
|
||||
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
revConsCand(tc, ap, config) and
|
||||
validAp(ap, config)
|
||||
}
|
||||
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
pragma[nomagic]
|
||||
private PathNodeImpl getANonHiddenSuccessor0() {
|
||||
result = this.getASuccessorIfHidden*() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
|
||||
not this.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
|
||||
@@ -163,7 +163,9 @@ abstract class Configuration extends string {
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
|
||||
predicate hasFlowTo(Node sink) {
|
||||
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
|
||||
* by `revFlow`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
revFlowConsCand(c, conf) and
|
||||
revFlowStore(c, _, _, conf)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableReturnPosOutNodeCandFwd1(
|
||||
additional predicate viableReturnPosOutNodeCandFwd1(
|
||||
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
||||
) {
|
||||
fwdFlowReturnPosition(pos, _, config) and
|
||||
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableParamArgNodeCandFwd1(
|
||||
additional predicate viableParamArgNodeCandFwd1(
|
||||
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
||||
) {
|
||||
viableParamArgEx(call, p, arg) and
|
||||
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlowState(FlowState state, Configuration config) {
|
||||
additional predicate revFlowState(FlowState state, Configuration config) {
|
||||
exists(NodeEx node |
|
||||
sinkNode(node, state, config) and
|
||||
revFlow(node, _, pragma[only_bind_into](config)) and
|
||||
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* argument.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate fwdFlow(
|
||||
additional predicate fwdFlow(
|
||||
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlow0(node, state, cc, argAp, ap, config) and
|
||||
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* the access path of the returned value.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlow(
|
||||
additional predicate revFlow(
|
||||
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlow0(node, state, toReturn, returnAp, ap, config) and
|
||||
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
revFlow(node, state, _, _, _, config)
|
||||
}
|
||||
|
||||
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
|
||||
additional predicate revFlowAlias(NodeEx node, Configuration config) {
|
||||
revFlow(node, _, _, _, _, config)
|
||||
}
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
revFlow(node, state, ap, config)
|
||||
}
|
||||
|
||||
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
revConsCand(tc, ap, config) and
|
||||
validAp(ap, config)
|
||||
}
|
||||
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
pragma[nomagic]
|
||||
private PathNodeImpl getANonHiddenSuccessor0() {
|
||||
result = this.getASuccessorIfHidden*() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
|
||||
not this.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
|
||||
@@ -163,7 +163,9 @@ abstract class Configuration extends string {
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
|
||||
predicate hasFlowTo(Node sink) {
|
||||
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
|
||||
* by `revFlow`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
|
||||
revFlowConsCand(c, conf) and
|
||||
revFlowStore(c, _, _, conf)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableReturnPosOutNodeCandFwd1(
|
||||
additional predicate viableReturnPosOutNodeCandFwd1(
|
||||
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
|
||||
) {
|
||||
fwdFlowReturnPosition(pos, _, config) and
|
||||
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate viableParamArgNodeCandFwd1(
|
||||
additional predicate viableParamArgNodeCandFwd1(
|
||||
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
|
||||
) {
|
||||
viableParamArgEx(call, p, arg) and
|
||||
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlowState(FlowState state, Configuration config) {
|
||||
additional predicate revFlowState(FlowState state, Configuration config) {
|
||||
exists(NodeEx node |
|
||||
sinkNode(node, state, config) and
|
||||
revFlow(node, _, pragma[only_bind_into](config)) and
|
||||
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* argument.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate fwdFlow(
|
||||
additional predicate fwdFlow(
|
||||
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlow0(node, state, cc, argAp, ap, config) and
|
||||
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
* the access path of the returned value.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revFlow(
|
||||
additional predicate revFlow(
|
||||
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlow0(node, state, toReturn, returnAp, ap, config) and
|
||||
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
|
||||
revFlow(node, state, _, _, _, config)
|
||||
}
|
||||
|
||||
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
|
||||
additional predicate revFlowAlias(NodeEx node, Configuration config) {
|
||||
revFlow(node, _, _, _, _, config)
|
||||
}
|
||||
|
||||
// use an alias as a workaround for bad functionality-induced joins
|
||||
pragma[nomagic]
|
||||
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
|
||||
revFlow(node, state, ap, config)
|
||||
}
|
||||
|
||||
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
|
||||
revConsCand(tc, ap, config) and
|
||||
validAp(ap, config)
|
||||
}
|
||||
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(
|
||||
additional predicate stats(
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
|
||||
) {
|
||||
fwd = true and
|
||||
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
pragma[nomagic]
|
||||
private PathNodeImpl getANonHiddenSuccessor0() {
|
||||
result = this.getASuccessorIfHidden*() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
|
||||
not this.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
|
||||
@@ -735,6 +735,9 @@ class SummaryNode extends NodeImpl, TSummaryNode {
|
||||
|
||||
SummaryNode() { this = TSummaryNode(c, state) }
|
||||
|
||||
/** Gets the summarized callable that this node belongs to. */
|
||||
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = c }
|
||||
|
||||
override CfgScope getCfgScope() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
@@ -1162,8 +1165,8 @@ private module PostUpdateNodes {
|
||||
ExprPostUpdateNode() { this = TExprPostUpdateNode(e) }
|
||||
|
||||
override ExprNode getPreUpdateNode() {
|
||||
// For compund arguments, such as `m(if b then x else y)`, we want the leaf nodes
|
||||
// `[post] x` and `[post] y` to have two pre-update nodes: (1) the compund argument,
|
||||
// For compound arguments, such as `m(if b then x else y)`, we want the leaf nodes
|
||||
// `[post] x` and `[post] y` to have two pre-update nodes: (1) the compound argument,
|
||||
// `if b then x else y`; and the (2) the underlying expressions; `x` and `y`,
|
||||
// respectively.
|
||||
//
|
||||
|
||||
@@ -64,7 +64,7 @@ predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, LocalVariable v) {
|
||||
i = -1
|
||||
}
|
||||
|
||||
/** Holds if `bb` contains a caputured read of variable `v`. */
|
||||
/** Holds if `bb` contains a captured read of variable `v`. */
|
||||
pragma[noinline]
|
||||
private predicate hasCapturedVariableRead(Cfg::BasicBlock bb, LocalVariable v) {
|
||||
exists(LocalVariableReadAccess read |
|
||||
@@ -74,7 +74,7 @@ private predicate hasCapturedVariableRead(Cfg::BasicBlock bb, LocalVariable v) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `bb` contains a caputured write to variable `v`. */
|
||||
/** Holds if `bb` contains a captured write to variable `v`. */
|
||||
pragma[noinline]
|
||||
private predicate writesCapturedVariable(Cfg::BasicBlock bb, LocalVariable v) {
|
||||
exists(LocalVariableWriteAccess write |
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) taint tracking.
|
||||
* This file re-exports the local (intraprocedural) taint-tracking analysis
|
||||
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
|
||||
* exposed through the `Configuration` class. For some languages, this file
|
||||
* exists in several identical copies, allowing queries to use multiple
|
||||
* `Configuration` classes that depend on each other without introducing
|
||||
* mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural taint tracking analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the taint tracking library must define its own unique extension of
|
||||
* this abstract class.
|
||||
*
|
||||
* A taint-tracking configuration is a special data flow configuration
|
||||
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
|
||||
* necessarily preserve values but are still relevant from a taint tracking
|
||||
* perspective. (For example, string concatenation, where one of the operands
|
||||
* is tainted.)
|
||||
*
|
||||
* To create a configuration, extend this class with a subclass whose
|
||||
* characteristic predicate is a unique singleton string. For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isSanitizer`.
|
||||
* // Optionally override `isSanitizerIn`.
|
||||
* // Optionally override `isSanitizerOut`.
|
||||
* // Optionally override `isSanitizerGuard`.
|
||||
* // Optionally override `isAdditionalTaintStep`.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but it is unsupported to depend on
|
||||
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
|
||||
* overridden predicates that define sources, sinks, or additional steps.
|
||||
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
|
||||
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
|
||||
*/
|
||||
abstract class Configuration extends DataFlow::Configuration {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant taint source.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate isSource(DataFlow::Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant taint source with the given initial
|
||||
* `state`.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant taint sink
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate isSink(DataFlow::Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant taint sink accepting `state`.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { none() }
|
||||
|
||||
/** Holds if the node `node` is a taint sanitizer. */
|
||||
predicate isSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrier(DataFlow::Node node) {
|
||||
this.isSanitizer(node) or
|
||||
defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `node` is a taint sanitizer when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) { none() }
|
||||
|
||||
final override predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
|
||||
this.isSanitizer(node, state)
|
||||
}
|
||||
|
||||
/** Holds if taint propagation into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
|
||||
|
||||
/** Holds if taint propagation out of `node` is prohibited. */
|
||||
predicate isSanitizerOut(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint may propagate from `node1` to `node2` in addition to the normal data-flow and taint steps.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
this.isAdditionalTaintStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint may propagate from `node1` to `node2` in addition to the normal data-flow and taint steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(
|
||||
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
|
||||
DataFlow::FlowState state2
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
|
||||
DataFlow::FlowState state2
|
||||
) {
|
||||
this.isAdditionalTaintStep(node1, state1, node2, state2)
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import codeql.ruby.dataflow.internal.TaintTrackingPublic as Public
|
||||
|
||||
module Private {
|
||||
import codeql.ruby.dataflow.internal.DataFlowImplForRegExp as DataFlow
|
||||
import codeql.ruby.dataflow.internal.TaintTrackingPrivate
|
||||
}
|
||||
@@ -417,7 +417,7 @@ module Rbi {
|
||||
override ReturnType getReturnType() { result = ReturnsCall.super.getReturnType() }
|
||||
}
|
||||
|
||||
/** A call to `void` that spcifies that a given method does not return a useful value. */
|
||||
/** A call to `void` that specifies that a given method does not return a useful value. */
|
||||
class MethodVoidCall extends MethodReturnsTypeCall instanceof VoidCall {
|
||||
override ReturnType getReturnType() { result = VoidCall.super.getReturnType() }
|
||||
}
|
||||
@@ -448,7 +448,7 @@ module Rbi {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `void` that spcifies that a given proc or block does not return
|
||||
* A call to `void` that specifies that a given proc or block does not return
|
||||
* a useful value.
|
||||
*/
|
||||
class ProcVoidCall extends ProcReturnsTypeCall instanceof VoidCall {
|
||||
|
||||
@@ -6,7 +6,7 @@ private import codeql.ruby.ast.internal.TreeSitter
|
||||
/** A source file that contains generated code. */
|
||||
abstract class GeneratedCodeFile extends RubyFile { }
|
||||
|
||||
/** A file contining comments suggesting it contains generated code. */
|
||||
/** A file continuing comments suggesting it contains generated code. */
|
||||
class GeneratedCommentFile extends GeneratedCodeFile {
|
||||
GeneratedCommentFile() { this = any(GeneratedCodeComment c).getLocation().getFile() }
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ class ParamsSource extends Http::Server::RequestInputAccess::Range {
|
||||
|
||||
override string getSourceType() { result = "ActionController::Metal#params" }
|
||||
|
||||
override string getKind() { result = "parameter" }
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::parameterInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +154,7 @@ class CookiesSource extends Http::Server::RequestInputAccess::Range {
|
||||
|
||||
override string getSourceType() { result = "ActionController::Metal#cookies" }
|
||||
|
||||
override string getKind() { result = "cookie" }
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::cookieInputKind() }
|
||||
}
|
||||
|
||||
/** A call to `cookies` from within a controller. */
|
||||
@@ -167,6 +167,140 @@ private class ActionControllerParamsCall extends ActionControllerContextCall, Pa
|
||||
ActionControllerParamsCall() { this.getMethodName() = "params" }
|
||||
}
|
||||
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
private module Request {
|
||||
/**
|
||||
* A call to `request` from within a controller. This is an instance of
|
||||
* `ActionDispatch::Request`.
|
||||
*/
|
||||
private class RequestNode extends DataFlow::CallNode {
|
||||
RequestNode() {
|
||||
this.asExpr().getExpr() instanceof ActionControllerContextCall and
|
||||
this.getMethodName() = "request"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request`.
|
||||
*/
|
||||
private class RequestMethodCall extends DataFlow::CallNode {
|
||||
RequestMethodCall() {
|
||||
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class RequestInputAccess extends RequestMethodCall,
|
||||
Http::Server::RequestInputAccess::Range {
|
||||
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns request parameters.
|
||||
*/
|
||||
private class ParametersCall extends RequestInputAccess {
|
||||
ParametersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
|
||||
"filtered_parameters"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns part or all of the request path. */
|
||||
private class PathCall extends RequestInputAccess {
|
||||
PathCall() {
|
||||
this.getMethodName() =
|
||||
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns a specific request header. */
|
||||
private class HeadersCall extends RequestInputAccess {
|
||||
HeadersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
|
||||
"host_authority", "content_type", "host", "hostname", "accept_encoding",
|
||||
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
|
||||
]
|
||||
or
|
||||
// Request headers are prefixed with `HTTP_` to distinguish them from
|
||||
// "headers" supplied by Rack middleware.
|
||||
this.getMethodName() = ["get_header", "fetch_header"] and
|
||||
this.getArgument(0).asExpr().getExpr().getConstantValue().getString().regexpMatch("^HTTP_.+")
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
// TODO: each_header
|
||||
/**
|
||||
* A method call on `request` which returns part or all of the host.
|
||||
* This can be influenced by headers such as Host and X-Forwarded-Host.
|
||||
*/
|
||||
private class HostCall extends RequestInputAccess {
|
||||
HostCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
|
||||
"forwarded_host", "port", "forwarded_port"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which is influenced by one or more request
|
||||
* headers.
|
||||
*/
|
||||
private class HeaderTaintedCall extends RequestInputAccess {
|
||||
HeaderTaintedCall() {
|
||||
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns the request body. */
|
||||
private class BodyCall extends RequestInputAccess {
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post"] }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns the rack env.
|
||||
* This is a hash containing all the information about the request. Values
|
||||
* under keys starting with `HTTP_` are user-controlled.
|
||||
*/
|
||||
private class EnvCall extends RequestMethodCall {
|
||||
EnvCall() { this.getMethodName() = ["env", "filtered_env"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of a user-controlled parameter from the request env.
|
||||
*/
|
||||
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
|
||||
EnvHttpAccess() {
|
||||
any(EnvCall c).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver()) and
|
||||
this.getMethodName() = "[]" and
|
||||
this.getArgument(0).asExpr().getExpr().getConstantValue().getString().regexpMatch("^HTTP_.+")
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
|
||||
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `render` from within a controller. */
|
||||
private class ActionControllerRenderCall extends ActionControllerContextCall, RenderCallImpl {
|
||||
ActionControllerRenderCall() { this.getMethodName() = "render" }
|
||||
|
||||
53
ruby/ql/lib/codeql/ruby/frameworks/ActionMailer.qll
Normal file
53
ruby/ql/lib/codeql/ruby/frameworks/ActionMailer.qll
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Provides modeling for the `ActionMailer` library.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.internal.Rails
|
||||
|
||||
/**
|
||||
* Provides modeling for the `ActionMailer` library.
|
||||
*/
|
||||
module ActionMailer {
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that extends `ActionMailer::Base`.
|
||||
* For example,
|
||||
*
|
||||
* ```rb
|
||||
* class FooMailer < ActionMailer::Base
|
||||
* ...
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class MailerClass extends ClassDeclaration {
|
||||
MailerClass() {
|
||||
this.getSuperclassExpr() =
|
||||
[
|
||||
API::getTopLevelMember("ActionMailer").getMember("Base"),
|
||||
// In Rails applications `ApplicationMailer` typically extends
|
||||
// `ActionMailer::Base`, but we treat it separately in case the
|
||||
// `ApplicationMailer` definition is not in the database.
|
||||
API::getTopLevelMember("ApplicationMailer")
|
||||
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call with a `self` receiver from within a mailer class */
|
||||
private class ContextCall extends MethodCall {
|
||||
private MailerClass mailerClass;
|
||||
|
||||
ContextCall() {
|
||||
this.getReceiver() instanceof SelfVariableAccess and
|
||||
this.getEnclosingModule() = mailerClass
|
||||
}
|
||||
|
||||
/** Gets the mailer class containing this method. */
|
||||
MailerClass getMailerClass() { result = mailerClass }
|
||||
}
|
||||
|
||||
/** A call to `params` from within a mailer. */
|
||||
class ParamsCall extends ContextCall, ParamsCallImpl {
|
||||
ParamsCall() { this.getMethodName() = "params" }
|
||||
}
|
||||
}
|
||||
30
ruby/ql/lib/codeql/ruby/frameworks/ActiveJob.qll
Normal file
30
ruby/ql/lib/codeql/ruby/frameworks/ActiveJob.qll
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Modeling for `ActiveJob`, a framweork for declaring and enqueueing jobs that
|
||||
* ships with Rails.
|
||||
* https://rubygems.org/gems/activejob
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
|
||||
/** Modeling for `ActiveJob`. */
|
||||
module ActiveJob {
|
||||
/**
|
||||
* `ActiveJob::Serializers`
|
||||
*/
|
||||
module Serializers {
|
||||
/**
|
||||
* A call to `ActiveJob::Serializers.deserialize`, which interprets part of
|
||||
* its argument as a Ruby constant.
|
||||
*/
|
||||
class DeserializeCall extends DataFlow::CallNode, CodeExecution::Range {
|
||||
DeserializeCall() {
|
||||
this =
|
||||
API::getTopLevelMember("ActiveJob").getMember("Serializers").getAMethodCall("deserialize")
|
||||
}
|
||||
|
||||
override DataFlow::Node getCode() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,25 @@ module ActiveSupport {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type summaries for extensions to the `Pathname` module.
|
||||
*/
|
||||
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
// Pathname#existence : Pathname
|
||||
row = ";Pathname;;Pathname;Method[existence].ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint flow summaries for extensions to the `Pathname` module. */
|
||||
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
// Pathname#existence
|
||||
row = ";Pathname;Method[existence];Argument[self];ReturnValue;taint"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `ActiveSupport::SafeBuffer` wraps a string, providing HTML-safe methods
|
||||
* for concatenation.
|
||||
|
||||
@@ -58,7 +58,7 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
|
||||
private class SplatSummary extends SummarizedCallable {
|
||||
SplatSummary() { this = "*(splat)" }
|
||||
|
||||
override SplatExpr getACall() { any() }
|
||||
override SplatExpr getACallSimple() { any() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
|
||||
@@ -123,7 +123,7 @@ class GraphqlSchemaObjectClass extends ClassDeclaration {
|
||||
* `GraphQL::Schema::RelayClassicMutation` or
|
||||
* `GraphQL::Schema::Resolver`.
|
||||
*
|
||||
* Both of these classes have an overrideable `resolve` instance
|
||||
* Both of these classes have an overridable `resolve` instance
|
||||
* method which can receive user input in order to resolve a query or mutation.
|
||||
*/
|
||||
private class GraphqlResolvableClass extends ClassDeclaration {
|
||||
@@ -147,7 +147,7 @@ private class GraphqlResolvableClass extends ClassDeclaration {
|
||||
*
|
||||
* ```rb
|
||||
* module Mutation
|
||||
* class NameAnInstrument < BaseMutationn
|
||||
* class NameAnInstrument < BaseMutation
|
||||
* argument :instrument_uuid, Types::Uuid,
|
||||
* required: true,
|
||||
* loads: ::Instrument,
|
||||
@@ -193,7 +193,7 @@ class GraphqlResolveMethod extends Method, Http::Server::RequestHandler::Range {
|
||||
*
|
||||
* ```rb
|
||||
* module Mutation
|
||||
* class NameAnInstrument < BaseMutationn
|
||||
* class NameAnInstrument < BaseMutation
|
||||
* argument :instrument_uuid, Types::Uuid,
|
||||
* required: true,
|
||||
* loads: ::Instrument,
|
||||
|
||||
@@ -71,6 +71,21 @@ module Rails {
|
||||
|
||||
/** A render call that does not automatically set the HTTP response body. */
|
||||
class RenderToCall extends MethodCall instanceof RenderToCallImpl { }
|
||||
|
||||
/**
|
||||
* A `render` call seen as a file system access.
|
||||
*/
|
||||
private class RenderAsFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
|
||||
RenderAsFileSystemAccess() {
|
||||
exists(MethodCall call | this.asExpr().getExpr() = call |
|
||||
call instanceof RenderCall
|
||||
or
|
||||
call instanceof RenderToCall
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getKeywordArgument("file") }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ module IO {
|
||||
* popen([env,] cmd, mode="r" [, opt]) -> io
|
||||
* popen([env,] cmd, mode="r" [, opt]) {|io| block } -> obj
|
||||
* ```
|
||||
* `IO.popen` does different things based on the the value of `cmd`:
|
||||
* `IO.popen` does different things based on the value of `cmd`:
|
||||
* ```
|
||||
* "-" : fork
|
||||
* commandline : command line string which is passed to a shell
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.Regexp as RE
|
||||
@@ -107,6 +107,18 @@ module String {
|
||||
preservesValue = false
|
||||
}
|
||||
|
||||
/** A `String` callable with a flow summary. */
|
||||
abstract class SummarizedCallable extends FlowSummary::SummarizedCallable {
|
||||
bindingset[this]
|
||||
SummarizedCallable() { any() }
|
||||
}
|
||||
|
||||
abstract private class SimpleSummarizedCallable extends SummarizedCallable,
|
||||
FlowSummary::SimpleSummarizedCallable {
|
||||
bindingset[this]
|
||||
SimpleSummarizedCallable() { any() }
|
||||
}
|
||||
|
||||
private class NewSummary extends SummarizedCallable {
|
||||
NewSummary() { this = "String.new" }
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import internal.ApiGraphModels as Shared
|
||||
private import internal.ApiGraphModelsSpecific as Specific
|
||||
import Shared::ModelInput as ModelInput
|
||||
import Shared::ModelOutput as ModelOutput
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
|
||||
/**
|
||||
* A remote flow source originating from a CSV source row.
|
||||
@@ -29,3 +31,33 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
|
||||
|
||||
override string getSourceType() { result = "Remote flow (from model)" }
|
||||
}
|
||||
|
||||
private class SummarizedCallableFromModel extends SummarizedCallable {
|
||||
string package;
|
||||
string type;
|
||||
string path;
|
||||
|
||||
SummarizedCallableFromModel() {
|
||||
ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
|
||||
this = package + ";" + type + ";" + path
|
||||
}
|
||||
|
||||
override Call getACall() {
|
||||
exists(API::MethodAccessNode base |
|
||||
ModelOutput::resolvedSummaryBase(package, type, path, base) and
|
||||
result = base.getCallNode().asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
exists(string kind |
|
||||
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
||||
|
|
||||
kind = "value" and
|
||||
preservesValue = true
|
||||
or
|
||||
kind = "taint" and
|
||||
preservesValue = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -71,6 +71,7 @@ class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNod
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -25,7 +25,7 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
[
|
||||
// One-off requests
|
||||
API::getTopLevelMember("HTTPClient"),
|
||||
// Conncection re-use
|
||||
// Connection re-use
|
||||
API::getTopLevelMember("HTTPClient").getInstance()
|
||||
] and
|
||||
requestNode = connectionNode.getReturn(method) and
|
||||
@@ -61,6 +61,7 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
.getArgument(0)
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -53,6 +53,7 @@ class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
result = this.getKeywordArgumentIncludeHashArgument(["verify", "verify_peer"])
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -80,6 +80,7 @@ class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -42,6 +42,7 @@ class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
|
||||
result = this.getKeywordArgumentIncludeHashArgument("ssl_verify_mode")
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
@@ -91,6 +92,7 @@ class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::C
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -54,6 +54,7 @@ class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::Call
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -34,6 +34,7 @@ class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNo
|
||||
result = this.getKeywordArgumentIncludeHashArgument("ssl_verifypeer")
|
||||
}
|
||||
|
||||
cached
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
|
||||
@@ -27,6 +27,7 @@ module Pathname {
|
||||
* Every `PathnameInstance` is considered to be a `FileNameSource`.
|
||||
*/
|
||||
class PathnameInstance extends FileNameSource {
|
||||
cached
|
||||
PathnameInstance() { any(PathnameConfiguration c).hasFlowTo(this) }
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ abstract class RegExp extends Ast::StringlikeLiteral {
|
||||
|
||||
/**
|
||||
* Helper predicate for `escapingChar`.
|
||||
* In order to avoid negative recusrion, we return a boolean.
|
||||
* In order to avoid negative recursion, we return a boolean.
|
||||
* This way, we can refer to `escaping(pos - 1).booleanNot()`
|
||||
* rather than to a negated version of `escaping(pos)`.
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
private import codeql.ruby.Regexp
|
||||
private import codeql.ruby.ast.Literal as Ast
|
||||
private import codeql.ruby.AST as Ast
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.dataflow.internal.tainttrackingforregexp.TaintTrackingImpl
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplForRegExp
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
|
||||
private import codeql.ruby.frameworks.core.String
|
||||
|
||||
class RegExpConfiguration extends Configuration {
|
||||
RegExpConfiguration() { this = "RegExpConfiguration" }
|
||||
@@ -20,7 +25,7 @@ class RegExpConfiguration extends Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof RegExpInterpretation::Range }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
exists(DataFlow::CallNode mce | mce.getMethodName() = ["match", "match?"] |
|
||||
// receiver of https://ruby-doc.org/core-2.4.0/String.html#method-i-match
|
||||
node = mce.getReceiver() and
|
||||
@@ -31,6 +36,24 @@ class RegExpConfiguration extends Configuration {
|
||||
mce.getReceiver() = trackRegexpType()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// include taint flow through `String` summaries,
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false) 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()
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides precicates for reasoning about bad tag filter vulnerabilities.
|
||||
* Provides predicates for reasoning about bad tag filter vulnerabilities.
|
||||
*/
|
||||
|
||||
import regexp.RegexpMatching
|
||||
@@ -65,7 +65,7 @@ predicate isBadRegexpFilter(HtmlMatchingRegExp regexp, string msg) {
|
||||
regexp.matches("<!-- foo --!>") and
|
||||
exists(int a, int b | a != b |
|
||||
regexp.fillsCaptureGroup("<!-- foo -->", a) and
|
||||
// <!-- foo --> might be ambigously parsed (matching both capture groups), and that is ok here.
|
||||
// <!-- foo --> might be ambiguously parsed (matching both capture groups), and that is ok here.
|
||||
regexp.fillsCaptureGroup("<!-- foo --!>", b) and
|
||||
not regexp.fillsCaptureGroup("<!-- foo --!>", a) and
|
||||
msg =
|
||||
|
||||
36
ruby/ql/lib/codeql/ruby/security/KernelOpenQuery.qll
Normal file
36
ruby/ql/lib/codeql/ruby/security/KernelOpenQuery.qll
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Provides utility classes and predicates for reasoning about `Kernel.open` and related methods.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.core.Kernel::Kernel
|
||||
|
||||
/** A call to a method that might access a file or start a process. */
|
||||
class AmbiguousPathCall extends DataFlow::CallNode {
|
||||
string name;
|
||||
|
||||
AmbiguousPathCall() {
|
||||
this.(KernelMethodCall).getMethodName() = "open" and
|
||||
name = "Kernel.open"
|
||||
or
|
||||
this = API::getTopLevelMember("IO").getAMethodCall("read") and
|
||||
not this = API::getTopLevelMember("File").getAMethodCall("read") and // needed in e.g. opal/opal, where some calls have both paths, but I'm not sure why
|
||||
name = "IO.read"
|
||||
}
|
||||
|
||||
/** Gets the name for the method being called. */
|
||||
string getName() { result = name }
|
||||
|
||||
/** Gets the name for a safer method that can be used instead. */
|
||||
string getReplacement() {
|
||||
result = "File.read" and name = "IO.read"
|
||||
or
|
||||
result = "File.open" and name = "Kernel.open"
|
||||
}
|
||||
|
||||
/** Gets the argument that specifies the path to be accessed. */
|
||||
DataFlow::Node getPathArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
@@ -8,6 +8,8 @@ private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.frameworks.ActiveJob
|
||||
private import codeql.ruby.frameworks.core.Module
|
||||
|
||||
module UnsafeDeserialization {
|
||||
/**
|
||||
@@ -199,4 +201,27 @@ module UnsafeDeserialization {
|
||||
toNode = callNode
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A argument in a call to `Module.const_get`, considered as a sink for unsafe
|
||||
* deserialization.
|
||||
*
|
||||
* Calls to `Module.const_get` can return arbitrary classes which can then be
|
||||
* instantiated.
|
||||
*/
|
||||
class ConstGetCallArgument extends Sink {
|
||||
ConstGetCallArgument() { this = any(Module::ModuleConstGetCallCodeExecution c).getCode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A argument in a call to `ActiveJob::Serializers.deserialize`, considered as
|
||||
* a sink for unsafe deserialization.
|
||||
*
|
||||
* This is roughly equivalent to a call to `Module.const_get`.
|
||||
*/
|
||||
class ActiveJobSerializersDeserializeArgument extends Sink {
|
||||
ActiveJobSerializersDeserializeArgument() {
|
||||
this = any(ActiveJob::Serializers::DeserializeCall c).getCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,9 @@ module UrlRedirect {
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
class HttpRequestInputAccessAsSource extends Source, Http::Server::RequestInputAccess {
|
||||
HttpRequestInputAccessAsSource() { this.isThirdPartyControllable() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A HTTP redirect response, considered as a flow sink.
|
||||
|
||||
@@ -312,9 +312,11 @@ module ReflectedXss {
|
||||
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
* A HTTP request input, considered as a flow source.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
class HttpRequestInputAccessAsSource extends Source, Http::Server::RequestInputAccess {
|
||||
HttpRequestInputAccessAsSource() { this.isThirdPartyControllable() }
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for ReflectedXss */
|
||||
|
||||
@@ -202,7 +202,7 @@ private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, Stat
|
||||
//
|
||||
// We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
|
||||
// This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
|
||||
// The below code is therefore a heuritic, that only flags regular expressions such as `/(a*)*b/`,
|
||||
// The below code is therefore a heuristic, that only flags regular expressions such as `/(a*)*b/`,
|
||||
// and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
|
||||
r1 = r2 and
|
||||
q1 = q2 and
|
||||
|
||||
@@ -59,8 +59,8 @@ predicate matchesEpsilon(RegExpTerm t) {
|
||||
/**
|
||||
* A lookahead/lookbehind that matches the empty string.
|
||||
*/
|
||||
class EmptyPositiveSubPatttern extends RegExpSubPattern {
|
||||
EmptyPositiveSubPatttern() {
|
||||
class EmptyPositiveSubPattern extends RegExpSubPattern {
|
||||
EmptyPositiveSubPattern() {
|
||||
(
|
||||
this instanceof RegExpPositiveLookahead
|
||||
or
|
||||
@@ -70,6 +70,9 @@ class EmptyPositiveSubPatttern extends RegExpSubPattern {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `EmptyPositiveSubPattern` instead. */
|
||||
deprecated class EmptyPositiveSubPatttern = EmptyPositiveSubPattern;
|
||||
|
||||
/**
|
||||
* A branch in a disjunction that is the root node in a literal, or a literal
|
||||
* whose root node is not a disjunction.
|
||||
@@ -133,7 +136,7 @@ private predicate isCanonicalTerm(RelevantRegExpTerm term, string str) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string reperesentation of the flags used with the regular expression.
|
||||
* Gets a string representation of the flags used with the regular expression.
|
||||
* Only the flags that are relevant for the canonicalization are included.
|
||||
*/
|
||||
string getCanonicalizationFlags(RegExpTerm root) {
|
||||
@@ -334,7 +337,7 @@ private module CharacterClasses {
|
||||
)
|
||||
}
|
||||
|
||||
private string lowercaseLetter() { result = "abdcefghijklmnopqrstuvwxyz".charAt(_) }
|
||||
private string lowercaseLetter() { result = "abcdefghijklmnopqrstuvwxyz".charAt(_) }
|
||||
|
||||
private string upperCaseLetter() { result = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(_) }
|
||||
|
||||
@@ -697,9 +700,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
|
||||
lbl = Epsilon() and q2 = Accept(getRoot(dollar))
|
||||
)
|
||||
or
|
||||
exists(EmptyPositiveSubPatttern empty | q1 = before(empty) |
|
||||
lbl = Epsilon() and q2 = after(empty)
|
||||
)
|
||||
exists(EmptyPositiveSubPattern empty | q1 = before(empty) | lbl = Epsilon() and q2 = after(empty))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1028,7 +1029,7 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* as the suffix "X" will cause both the regular expressions to be rejected.
|
||||
*
|
||||
* The string `w` is repeated any number of times because it needs to be
|
||||
* infinitely repeatedable for the attack to work.
|
||||
* infinitely repeatable for the attack to work.
|
||||
* For the regular expression `/((ab)+)*abab/` the accepting state is not reachable from the fork
|
||||
* using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@ import codeql.Locations
|
||||
private import codeql.ruby.ast.Literal as Ast
|
||||
|
||||
/**
|
||||
* Holds if `term` is an ecape class representing e.g. `\d`.
|
||||
* Holds if `term` is an escape class representing e.g. `\d`.
|
||||
* `clazz` is which character class it represents, e.g. "d" for `\d`.
|
||||
*/
|
||||
predicate isEscapeClass(RegExpTerm term, string clazz) {
|
||||
|
||||
@@ -106,6 +106,18 @@ module PolynomialReDoS {
|
||||
regexp.asExpr() = call.getReceiver() and
|
||||
this.asExpr() = call.getArgument(0)
|
||||
)
|
||||
or
|
||||
// a case-when statement
|
||||
exists(CfgNodes::ExprNodes::CaseExprCfgNode caseWhen |
|
||||
matchNode.asExpr() = caseWhen and
|
||||
this.asExpr() = caseWhen.getValue()
|
||||
|
|
||||
regexp.asExpr() =
|
||||
caseWhen.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_)
|
||||
or
|
||||
regexp.asExpr() =
|
||||
caseWhen.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides precicates for reasoning about which strings are matched by a regular expression,
|
||||
* Provides predicates for reasoning about which strings are matched by a regular expression,
|
||||
* and for testing which capture groups are filled when a particular regexp matches a string.
|
||||
*/
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class StateTuple extends TStateTuple {
|
||||
StateTuple() { this = MkStateTuple(q1, q2, q3) }
|
||||
|
||||
/**
|
||||
* Gest a string repesentation of this tuple.
|
||||
* Gest a string representation of this tuple.
|
||||
*/
|
||||
string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ private module Cached {
|
||||
jumpStep(nodeFrom, nodeTo) and
|
||||
summary = JumpStep()
|
||||
or
|
||||
levelStep(nodeFrom, nodeTo) and
|
||||
levelStepNoCall(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
exists(TypeTrackerContent content |
|
||||
@@ -216,6 +216,9 @@ private module Cached {
|
||||
or
|
||||
returnStep(nodeFrom, nodeTo) and
|
||||
summary = ReturnStep()
|
||||
or
|
||||
levelStepCall(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,20 +76,28 @@ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
|
||||
*/
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
|
||||
/**
|
||||
* Holds if there is a summarized local flow step from `nodeFrom` to `nodeTo`,
|
||||
* because there is direct flow from a parameter to a return. That is, summarized
|
||||
* steps are not applied recursively.
|
||||
*/
|
||||
/** Holds if there is direct flow from `param` to a return. */
|
||||
pragma[nomagic]
|
||||
private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(DataFlowPublic::ParameterNode param, DataFlowPrivate::ReturningNode returnNode |
|
||||
private predicate flowThrough(DataFlowPublic::ParameterNode param) {
|
||||
exists(DataFlowPrivate::ReturningNode returnNode |
|
||||
DataFlowPrivate::LocalFlow::getParameterDefNode(param.getParameter())
|
||||
.(TypeTrackingNode)
|
||||
.flowsTo(returnNode) and
|
||||
.flowsTo(returnNode)
|
||||
)
|
||||
}
|
||||
|
||||
/** 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)
|
||||
)
|
||||
or
|
||||
}
|
||||
|
||||
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
|
||||
pragma[nomagic]
|
||||
predicate levelStepNoCall(Node nodeFrom, Node nodeTo) {
|
||||
exists(
|
||||
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
@@ -99,10 +107,65 @@ private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
or
|
||||
localFieldStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/** Holds if there is a level step from `nodeFrom` to `nodeTo`. */
|
||||
predicate levelStep(Node nodeFrom, Node nodeTo) { summarizedLocalStep(nodeFrom, nodeTo) }
|
||||
/**
|
||||
* Gets a method of `mod`, with `instance` indicating if this is an instance method.
|
||||
*
|
||||
* Does not take inheritance or the various forms of inclusion into account.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private MethodBase getAMethod(ModuleBase mod, boolean instance) {
|
||||
not mod instanceof SingletonClass and
|
||||
result = mod.getAMethod() and
|
||||
if result instanceof SingletonMethod then instance = false else instance = true
|
||||
or
|
||||
exists(SingletonClass cls |
|
||||
cls.getValue().(SelfVariableAccess).getVariable().getDeclaringScope() = mod and
|
||||
result = cls.getAMethod().(Method) and
|
||||
instance = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value flowing into `field` in `mod`, with `instance` indicating if it's
|
||||
* a field on an instance of `mod` (as opposed to the module object itself).
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private Node fieldPredecessor(ModuleBase mod, boolean instance, string field) {
|
||||
exists(InstanceVariableWriteAccess access, AssignExpr assign |
|
||||
access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and
|
||||
field = access.getVariable().getName() and
|
||||
assign.getLeftOperand() = access and
|
||||
result.asExpr().getExpr() = assign.getRightOperand()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to `field` in `mod`, with `instance` indicating if it's
|
||||
* a field on an instance of `mod` (as opposed to the module object itself).
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private Node fieldSuccessor(ModuleBase mod, boolean instance, string field) {
|
||||
exists(InstanceVariableReadAccess access |
|
||||
access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and
|
||||
result.asExpr().getExpr() = access and
|
||||
field = access.getVariable().getName()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` should be used a level step, from a field assignment to
|
||||
* a read within the same class.
|
||||
*/
|
||||
private predicate localFieldStep(Node pred, Node succ) {
|
||||
exists(ModuleBase mod, boolean instance, string field |
|
||||
pred = fieldPredecessor(mod, instance, field) and
|
||||
succ = fieldSuccessor(mod, instance, field)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate argumentPositionMatch(
|
||||
@@ -325,9 +388,18 @@ private predicate hasStoreSummary(
|
||||
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
) {
|
||||
callable.propagatesFlow(input, push(SummaryComponent::content(contents), output), true) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head())
|
||||
not isNonLocal(output.head()) and
|
||||
(
|
||||
callable.propagatesFlow(input, push(SummaryComponent::content(contents), output), true)
|
||||
or
|
||||
// Allow the input to start with an arbitrary WithoutContent[X].
|
||||
// Since type-tracking only tracks one content deep, and we're about to store into another content,
|
||||
// we're already preventing the input from being in a content.
|
||||
callable
|
||||
.propagatesFlow(push(SummaryComponent::withoutContent(_), input),
|
||||
push(SummaryComponent::content(contents), output), true)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -460,6 +532,9 @@ private predicate dependsOnSummaryComponentStack(
|
||||
callable.propagatesFlow(stack, _, true)
|
||||
or
|
||||
callable.propagatesFlow(_, stack, true)
|
||||
or
|
||||
// include store summaries as they may skip an initial step at the input
|
||||
hasStoreSummary(callable, _, stack, _)
|
||||
)
|
||||
or
|
||||
dependsOnSummaryComponentStackCons(callable, _, stack)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `rb/log-inection`, to detect cases where a malicious user may be able to forge log entries.
|
||||
* Added a new query, `rb/log-injection`, to detect cases where a malicious user may be able to forge log entries.
|
||||
* Added a new query, `rb/incomplete-multi-character-sanitization`. The query
|
||||
finds string transformations that do not replace all occurrences of a
|
||||
multi-character substring.
|
||||
@@ -119,7 +119,7 @@
|
||||
### New Queries
|
||||
|
||||
* A new query (`rb/request-forgery`) has been added. The query finds HTTP requests made with user-controlled URLs.
|
||||
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explictly disabled.
|
||||
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explicitly disabled.
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rb/non-constant-kernel-open`, to detect uses of Kernel.open and related methods with non-constant values.
|
||||
4
ruby/ql/src/change-notes/2022-10-07-alert-messages.md
Normal file
4
ruby/ql/src/change-notes/2022-10-07-alert-messages.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The alert message of many queries have been changed to better follow the style guide and make the message consistent with other languages.
|
||||
4
ruby/ql/src/change-notes/2022-10-12-rails-render-file.md
Normal file
4
ruby/ql/src/change-notes/2022-10-12-rails-render-file.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `rb/path-injection` query now treats the `file:` argument of the Rails `render` method as a sink.
|
||||
@@ -3,7 +3,7 @@
|
||||
### New Queries
|
||||
|
||||
* A new query (`rb/request-forgery`) has been added. The query finds HTTP requests made with user-controlled URLs.
|
||||
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explictly disabled.
|
||||
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explicitly disabled.
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `rb/log-inection`, to detect cases where a malicious user may be able to forge log entries.
|
||||
* Added a new query, `rb/log-injection`, to detect cases where a malicious user may be able to forge log entries.
|
||||
* Added a new query, `rb/incomplete-multi-character-sanitization`. The query
|
||||
finds string transformations that do not replace all occurrences of a
|
||||
multi-character substring.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @name Manually checking http verb instead of using built in rails routes and protections
|
||||
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods.
|
||||
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mapping resources and verbs to specific methods.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.0
|
||||
@@ -93,4 +93,4 @@ class HttpVerbConfig extends TaintTracking::Configuration {
|
||||
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods."
|
||||
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mapping resources and verbs to specific methods."
|
||||
|
||||
28
ruby/ql/src/queries/meta/TaintedNodes.ql
Normal file
28
ruby/ql/src/queries/meta/TaintedNodes.ql
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name Tainted nodes
|
||||
* @description Nodes reachable from a remote flow source via default taint-tracking steps.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id rb/meta/tainted-nodes
|
||||
* @tags meta
|
||||
* @precision very-low
|
||||
*/
|
||||
|
||||
import internal.TaintMetrics
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
|
||||
class BasicTaintConfiguration extends TaintTracking::Configuration {
|
||||
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource(_) }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
// To reduce noise from synthetic nodes, only count nodes that have an associated expression.
|
||||
exists(node.asExpr().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::Node node
|
||||
where any(BasicTaintConfiguration cfg).hasFlow(_, node)
|
||||
select node, "Tainted node"
|
||||
@@ -36,3 +36,10 @@ DataFlow::Node relevantTaintSink(string kind) {
|
||||
kind = "UrlRedirect" and result instanceof UrlRedirect::Sink
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root folder of the snapshot.
|
||||
*
|
||||
* This is selected as the location for project-wide metrics.
|
||||
*/
|
||||
Folder projectRoot() { result.getRelativePath() = "" }
|
||||
|
||||
46
ruby/ql/src/queries/security/cwe-078/KernelOpen.inc.qhelp
Normal file
46
ruby/ql/src/queries/security/cwe-078/KernelOpen.inc.qhelp
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If <code>Kernel.open</code> is given a file name that starts with a <code>|</code>
|
||||
character, it will execute the remaining string as a shell command. If a
|
||||
malicious user can control the file name, they can execute arbitrary code.
|
||||
The same vulnerability applies to <code>IO.read</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Use <code>File.open</code> instead of <code>Kernel.open</code>, as the former
|
||||
does not have this vulnerability. Similarly, use <code>File.read</code> instead
|
||||
of <code>IO.read</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following example shows code that calls <code>Kernel.open</code> on a
|
||||
user-supplied file path.
|
||||
</p>
|
||||
|
||||
<sample src="examples/kernel_open.rb" />
|
||||
|
||||
<p>Instead, <code>File.open</code> should be used, as in the following example.</p>
|
||||
|
||||
<sample src="examples/file_open.rb" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Example CVE: <a href="https://www.ruby-lang.org/en/news/2021/05/02/os-command-injection-in-rdoc/">Command Injection in RDoc</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,46 +1,4 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If <code>Kernel.open</code> is given a file name that starts with a <code>|</code>
|
||||
character, it will execute the remaining string as a shell command. If a
|
||||
malicious user can control the file name, they can execute arbitrary code.
|
||||
The same vulnerability applies to <code>IO.read</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Use <code>File.open</code> instead of <code>Kernel.open</code>, as the former
|
||||
does not have this vulnerability. Similarly, use <code>File.read</code> instead
|
||||
of <code>IO.read</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following example shows code that calls <code>Kernel.open</code> on a
|
||||
user-supplied file path.
|
||||
</p>
|
||||
|
||||
<sample src="examples/kernel_open.rb" />
|
||||
|
||||
<p>Instead, <code>File.open</code> should be used, as in the following example.</p>
|
||||
|
||||
<sample src="examples/file_open.rb" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Example CVE: <a href="https://www.ruby-lang.org/en/news/2021/05/02/os-command-injection-in-rdoc/">Command Injection in RDoc</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
<include src="KernelOpen.inc.qhelp" />
|
||||
</qhelp>
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Use of `Kernel.open` or `IO.read`
|
||||
* @name Use of `Kernel.open` or `IO.read` with user-controlled input
|
||||
* @description Using `Kernel.open` or `IO.read` may allow a malicious
|
||||
* user to execute arbitrary system commands.
|
||||
* @kind path-problem
|
||||
@@ -14,39 +14,12 @@
|
||||
* external/cwe/cwe-073
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.frameworks.core.Kernel::Kernel
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A method call that has a suggested replacement.
|
||||
*/
|
||||
abstract class Replacement extends DataFlow::CallNode {
|
||||
abstract string getFrom();
|
||||
|
||||
abstract string getTo();
|
||||
}
|
||||
|
||||
class KernelOpenCall extends KernelMethodCall, Replacement {
|
||||
KernelOpenCall() { this.getMethodName() = "open" }
|
||||
|
||||
override string getFrom() { result = "Kernel.open" }
|
||||
|
||||
override string getTo() { result = "File.open" }
|
||||
}
|
||||
|
||||
class IOReadCall extends DataFlow::CallNode, Replacement {
|
||||
IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read") }
|
||||
|
||||
override string getFrom() { result = "IO.read" }
|
||||
|
||||
override string getTo() { result = "File.read" }
|
||||
}
|
||||
import codeql.ruby.security.KernelOpenQuery
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "KernelOpen" }
|
||||
@@ -54,9 +27,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(KernelOpenCall c | c.getArgument(0) = sink)
|
||||
or
|
||||
exists(IOReadCall c | c.getArgument(0) = sink)
|
||||
sink = any(AmbiguousPathCall r).getPathArgument()
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
@@ -73,5 +44,6 @@ where
|
||||
sourceNode = source.getNode() and
|
||||
call.getArgument(0) = sink.getNode()
|
||||
select sink.getNode(), source, sink,
|
||||
"This call to " + call.(Replacement).getFrom() + " depends on a $@. Replace it with " +
|
||||
call.(Replacement).getTo() + ".", source.getNode(), "user-provided value"
|
||||
"This call to " + call.(AmbiguousPathCall).getName() +
|
||||
" depends on a $@. Consider replacing it with " + call.(AmbiguousPathCall).getReplacement() +
|
||||
".", source.getNode(), "user-provided value"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="KernelOpen.inc.qhelp" />
|
||||
</qhelp>
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @name Use of `Kernel.open` or `IO.read` with a non-constant value
|
||||
* @description Using `Kernel.open` or `IO.read` may allow a malicious
|
||||
* user to execute arbitrary system commands.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 6.5
|
||||
* @precision high
|
||||
* @id rb/non-constant-kernel-open
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-088
|
||||
* external/cwe/cwe-073
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.KernelOpenQuery
|
||||
import codeql.ruby.ast.Literal
|
||||
|
||||
from AmbiguousPathCall call
|
||||
where
|
||||
// there is not a constant string argument
|
||||
not exists(call.getPathArgument().asExpr().getExpr().getConstantValue()) and
|
||||
// if it's a format string, then the first argument is not a constant string
|
||||
not call.getPathArgument().getALocalSource().asExpr().getExpr().(StringLiteral).getComponent(0)
|
||||
instanceof StringTextComponent
|
||||
select call,
|
||||
"Call to " + call.getName() + " with a non-constant value. Consider replacing it with " +
|
||||
call.getReplacement() + "."
|
||||
@@ -27,8 +27,6 @@ where
|
||||
// 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"
|
||||
if disablingNode.getLocation() = origin.getLocation() then ending = "." else ending = " by $@."
|
||||
select request, "This request may run without certificate validation because $@" + ending,
|
||||
disablingNode, "the request is disabled", origin, "this value"
|
||||
|
||||
@@ -20,5 +20,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Sensitive data returned by $@ is logged here.",
|
||||
select sink.getNode(), source, sink, "This logs sensitive data returned by $@ as clear text.",
|
||||
source.getNode(), source.getNode().(Source).describe()
|
||||
|
||||
@@ -21,5 +21,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Sensitive data returned by $@ is stored here.",
|
||||
select sink.getNode(), source, sink, "This stores sensitive data returned by $@ as clear text.",
|
||||
source.getNode(), source.getNode().(Source).describe()
|
||||
|
||||
@@ -12,7 +12,7 @@ to execute arbitrary code.
|
||||
<recommendation>
|
||||
<p>
|
||||
Avoid deserialization of untrusted data if possible. If the architecture permits
|
||||
it, use serialization formats that cannot represent arbitarary objects. For
|
||||
it, use serialization formats that cannot represent arbitrary objects. For
|
||||
libraries that support it, such as the Ruby standard library's <code>JSON</code>
|
||||
module, ensure that the parser is configured to disable
|
||||
deserialization of arbitrary objects.
|
||||
|
||||
@@ -63,5 +63,6 @@ from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf,
|
||||
FileSystemPermissionModification mod
|
||||
where conf.hasFlowPath(source, sink) and mod.getAPermissionNode() = sink.getNode()
|
||||
select source.getNode(), source, sink, "Overly permissive mask in $@ sets file to $@.", mod,
|
||||
mod.toString(), source.getNode(), source.getNode().toString()
|
||||
select source.getNode(), source, sink,
|
||||
"This overly permissive mask used in $@ allows read or write access to others.", mod,
|
||||
mod.toString()
|
||||
|
||||
@@ -154,4 +154,5 @@ class HardcodedCredentialsConfiguration extends DataFlow::Configuration {
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, HardcodedCredentialsConfiguration conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select source.getNode(), source, sink, "Use of $@.", source.getNode(), "hardcoded credentials"
|
||||
select source.getNode(), source, sink, "This hardcoded value is $@.", sink.getNode(),
|
||||
"used as credentials"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Downloading executeables or other sensitive files over an unencrypted connection
|
||||
Downloading executables or other sensitive files over an unencrypted connection
|
||||
can leave a server open to man-in-the-middle attacks (MITM).
|
||||
Such an attack can allow an attacker to insert arbitrary content
|
||||
into the downloaded file, and in the worst case, allow the attacker to execute
|
||||
|
||||
@@ -18,4 +18,5 @@ import codeql.ruby.security.HttpToFileAccessQuery
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to file system.", source.getNode(), "Untrusted data"
|
||||
select sink.getNode(), source, sink, "Write to file system depends on $@.", source.getNode(),
|
||||
"untrusted data"
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
| array_flow.rb:3:16:3:35 | # $ hasValueFlow=0.1 | Missing result:hasValueFlow=0.1 |
|
||||
| array_flow.rb:5:16:5:35 | # $ hasValueFlow=0.1 | Missing result:hasValueFlow=0.1 |
|
||||
| array_flow.rb:83:13:83:30 | # $ hasValueFlow=9 | Missing result:hasValueFlow=9 |
|
||||
| array_flow.rb:107:10:107:13 | ...[...] | Unexpected result: hasValueFlow=11.2 |
|
||||
| array_flow.rb:179:28:179:46 | # $ hasValueFlow=19 | Missing result:hasValueFlow=19 |
|
||||
| array_flow.rb:180:28:180:46 | # $ hasValueFlow=19 | Missing result:hasValueFlow=19 |
|
||||
|
||||
@@ -16,11 +16,16 @@ track
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | self in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:3:9:3:23 | call to puts | type tracker without call steps | type_tracker.rb:3:9:3:23 | call to puts |
|
||||
| type_tracker.rb:3:14:3:23 | call to field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:4:9:4:14 | @field | type tracker without call steps | type_tracker.rb:4:9:4:14 | @field |
|
||||
@@ -55,6 +60,7 @@ track
|
||||
| type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
@@ -368,11 +374,16 @@ trackEnd
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:3:9:3:23 | call to puts | type_tracker.rb:3:9:3:23 | call to puts |
|
||||
| type_tracker.rb:3:14:3:23 | call to field | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:4:9:4:14 | @field | type_tracker.rb:4:9:4:14 | @field |
|
||||
@@ -424,6 +435,7 @@ trackEnd
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:4:9:4:20 | ... = ... |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:4:18:4:20 | val |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:13 | __synth__0 |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:23 | ... |
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
actionControllerControllerClasses
|
||||
| action_controller/input_access.rb:1:1:50:3 | UsersController |
|
||||
| action_controller/params_flow.rb:1:1:151:3 | MyController |
|
||||
| active_record/ActiveRecord.rb:23:1:39:3 | FooController |
|
||||
| active_record/ActiveRecord.rb:41:1:64:3 | BarController |
|
||||
| active_record/ActiveRecord.rb:66:1:98:3 | BazController |
|
||||
| active_record/ActiveRecord.rb:100:1:108:3 | AnnotatedController |
|
||||
| active_storage/active_storage.rb:39:1:45:3 | PostsController |
|
||||
| app/controllers/comments_controller.rb:1:1:7:3 | CommentsController |
|
||||
| app/controllers/comments_controller.rb:1:1:14:3 | CommentsController |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController |
|
||||
| app/controllers/photos_controller.rb:1:1:4:3 | PhotosController |
|
||||
| app/controllers/posts_controller.rb:1:1:10:3 | PostsController |
|
||||
| app/controllers/tags_controller.rb:1:1:2:3 | TagsController |
|
||||
| app/controllers/users/notifications_controller.rb:2:3:5:5 | NotificationsController |
|
||||
actionControllerActionMethods
|
||||
| action_controller/input_access.rb:2:3:49:5 | index |
|
||||
| action_controller/params_flow.rb:2:3:4:5 | m1 |
|
||||
| action_controller/params_flow.rb:6:3:8:5 | m2 |
|
||||
| action_controller/params_flow.rb:10:3:12:5 | m2 |
|
||||
@@ -59,8 +61,8 @@ actionControllerActionMethods
|
||||
| active_record/ActiveRecord.rb:101:3:103:5 | index |
|
||||
| active_record/ActiveRecord.rb:105:3:107:5 | unsafe_action |
|
||||
| active_storage/active_storage.rb:40:3:44:5 | create |
|
||||
| app/controllers/comments_controller.rb:2:3:3:5 | index |
|
||||
| app/controllers/comments_controller.rb:5:3:6:5 | show |
|
||||
| app/controllers/comments_controller.rb:2:3:10:5 | index |
|
||||
| app/controllers/comments_controller.rb:12:3:13:5 | show |
|
||||
| app/controllers/foo/bars_controller.rb:5:3:7:5 | index |
|
||||
| app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug |
|
||||
| app/controllers/foo/bars_controller.rb:20:3:24:5 | show |
|
||||
@@ -115,6 +117,7 @@ paramsCalls
|
||||
| action_controller/params_flow.rb:144:10:144:15 | call to params |
|
||||
| action_controller/params_flow.rb:145:32:145:37 | call to params |
|
||||
| action_controller/params_flow.rb:148:22:148:27 | call to params |
|
||||
| action_mailer/mailer.rb:3:10:3:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
@@ -189,6 +192,7 @@ paramsSources
|
||||
| action_controller/params_flow.rb:144:10:144:15 | call to params |
|
||||
| action_controller/params_flow.rb:145:32:145:37 | call to params |
|
||||
| action_controller/params_flow.rb:148:22:148:27 | call to params |
|
||||
| action_mailer/mailer.rb:3:10:3:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
@@ -220,6 +224,137 @@ paramsSources
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
|
||||
httpInputAccesses
|
||||
| action_controller/input_access.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
|
||||
| action_controller/input_access.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
|
||||
| action_controller/input_access.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
|
||||
| action_controller/input_access.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
|
||||
| action_controller/input_access.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
|
||||
| action_controller/input_access.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
|
||||
| action_controller/input_access.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
|
||||
| action_controller/input_access.rb:11:5:11:25 | call to authorization | ActionDispatch::Request#authorization |
|
||||
| action_controller/input_access.rb:12:5:12:23 | call to script_name | ActionDispatch::Request#script_name |
|
||||
| action_controller/input_access.rb:13:5:13:21 | call to path_info | ActionDispatch::Request#path_info |
|
||||
| action_controller/input_access.rb:14:5:14:22 | call to user_agent | ActionDispatch::Request#user_agent |
|
||||
| action_controller/input_access.rb:15:5:15:19 | call to referer | ActionDispatch::Request#referer |
|
||||
| action_controller/input_access.rb:16:5:16:20 | call to referrer | ActionDispatch::Request#referrer |
|
||||
| action_controller/input_access.rb:17:5:17:26 | call to host_authority | ActionDispatch::Request#host_authority |
|
||||
| action_controller/input_access.rb:18:5:18:24 | call to content_type | ActionDispatch::Request#content_type |
|
||||
| action_controller/input_access.rb:19:5:19:16 | call to host | ActionDispatch::Request#host |
|
||||
| action_controller/input_access.rb:20:5:20:20 | call to hostname | ActionDispatch::Request#hostname |
|
||||
| action_controller/input_access.rb:21:5:21:27 | call to accept_encoding | ActionDispatch::Request#accept_encoding |
|
||||
| action_controller/input_access.rb:22:5:22:27 | call to accept_language | ActionDispatch::Request#accept_language |
|
||||
| action_controller/input_access.rb:23:5:23:25 | call to if_none_match | ActionDispatch::Request#if_none_match |
|
||||
| action_controller/input_access.rb:24:5:24:31 | call to if_none_match_etags | ActionDispatch::Request#if_none_match_etags |
|
||||
| action_controller/input_access.rb:25:5:25:29 | call to content_mime_type | ActionDispatch::Request#content_mime_type |
|
||||
| action_controller/input_access.rb:27:5:27:21 | call to authority | ActionDispatch::Request#authority |
|
||||
| action_controller/input_access.rb:28:5:28:16 | call to host | ActionDispatch::Request#host |
|
||||
| action_controller/input_access.rb:29:5:29:26 | call to host_authority | ActionDispatch::Request#host_authority |
|
||||
| action_controller/input_access.rb:30:5:30:26 | call to host_with_port | ActionDispatch::Request#host_with_port |
|
||||
| action_controller/input_access.rb:31:5:31:20 | call to hostname | ActionDispatch::Request#hostname |
|
||||
| action_controller/input_access.rb:32:5:32:25 | call to forwarded_for | ActionDispatch::Request#forwarded_for |
|
||||
| action_controller/input_access.rb:33:5:33:26 | call to forwarded_host | ActionDispatch::Request#forwarded_host |
|
||||
| action_controller/input_access.rb:34:5:34:16 | call to port | ActionDispatch::Request#port |
|
||||
| action_controller/input_access.rb:35:5:35:26 | call to forwarded_port | ActionDispatch::Request#forwarded_port |
|
||||
| action_controller/input_access.rb:37:5:37:22 | call to media_type | ActionDispatch::Request#media_type |
|
||||
| action_controller/input_access.rb:38:5:38:29 | call to media_type_params | ActionDispatch::Request#media_type_params |
|
||||
| action_controller/input_access.rb:39:5:39:27 | call to content_charset | ActionDispatch::Request#content_charset |
|
||||
| action_controller/input_access.rb:40:5:40:20 | call to base_url | ActionDispatch::Request#base_url |
|
||||
| action_controller/input_access.rb:42:5:42:16 | call to body | ActionDispatch::Request#body |
|
||||
| action_controller/input_access.rb:43:5:43:20 | call to raw_post | ActionDispatch::Request#raw_post |
|
||||
| action_controller/input_access.rb:45:5:45:30 | ...[...] | ActionDispatch::Request#env[] |
|
||||
| action_controller/input_access.rb:47:5:47:39 | ...[...] | ActionDispatch::Request#env[] |
|
||||
| action_controller/params_flow.rb:3:10:3:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:7:10:7:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:11:10:11:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:15:10:15:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:19:10:19:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:23:10:23:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:27:10:27:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:31:10:31:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:35:10:35:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:39:10:39:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:43:10:43:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:47:10:47:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:51:10:51:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:55:10:55:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:59:10:59:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:63:10:63:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:67:10:67:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:71:10:71:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:75:10:75:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:79:10:79:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:83:10:83:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:87:10:87:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:91:10:91:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:95:10:95:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:99:10:99:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:103:10:103:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:107:10:107:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:111:10:111:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:112:23:112:28 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:116:10:116:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:117:31:117:36 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:121:10:121:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:122:31:122:36 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:126:10:126:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:127:24:127:29 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:130:14:130:19 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:135:10:135:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:136:32:136:37 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:139:22:139:27 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:144:10:144:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:145:32:145:37 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:148:22:148:27 | call to params | ActionController::Metal#params |
|
||||
| action_mailer/mailer.rb:3:10:3:15 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:32:21:32:26 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:34:34:34:39 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:35:23:35:28 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:35:38:35:43 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:43:10:43:15 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:50:11:50:16 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:54:12:54:17 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:59:12:59:17 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:62:15:62:20 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:68:21:68:26 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:72:18:72:23 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:76:24:76:29 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:76:49:76:54 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:80:25:80:30 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:80:50:80:55 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:88:21:88:26 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:92:27:92:32 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:92:52:92:57 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:96:28:96:33 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:96:53:96:58 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:106:59:106:64 | call to params | ActionController::Metal#params |
|
||||
| active_storage/active_storage.rb:41:21:41:26 | call to params | ActionController::Metal#params |
|
||||
| active_storage/active_storage.rb:42:24:42:29 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/comments_controller.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
|
||||
| app/controllers/comments_controller.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
|
||||
| app/controllers/comments_controller.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
|
||||
| app/controllers/comments_controller.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
|
||||
| app/controllers/comments_controller.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
|
||||
| app/controllers/comments_controller.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
|
||||
| app/controllers/comments_controller.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies | ActionController::Metal#cookies |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params | ActionController::Metal#params |
|
||||
| app/graphql/mutations/dummy.rb:5:24:5:25 | id | GraphQL RoutedParameter |
|
||||
| app/graphql/mutations/dummy.rb:9:17:9:25 | something | GraphQL RoutedParameter |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id | GraphQL RoutedParameter |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:10:18:10:23 | number | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:18:23:18:33 | blah_number | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:27:20:27:25 | **args | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:36:34:36:37 | arg1 | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:36:41:36:46 | **rest | GraphQL RoutedParameter |
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params | ActionController::Metal#params |
|
||||
cookiesCalls
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
|
||||
cookiesSources
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
query predicate actionControllerControllerClasses(ActionControllerControllerClass cls) { any() }
|
||||
|
||||
@@ -10,6 +12,10 @@ query predicate paramsCalls(Rails::ParamsCall c) { any() }
|
||||
|
||||
query predicate paramsSources(ParamsSource src) { any() }
|
||||
|
||||
query predicate httpInputAccesses(Http::Server::RequestInputAccess a, string sourceType) {
|
||||
sourceType = a.getSourceType()
|
||||
}
|
||||
|
||||
query predicate cookiesCalls(Rails::CookiesCall c) { any() }
|
||||
|
||||
query predicate cookiesSources(CookiesSource src) { any() }
|
||||
|
||||
@@ -36,8 +36,8 @@ actionDispatchRoutes
|
||||
actionDispatchControllerMethods
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:2:3:3:5 | index |
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:5:3:6:5 | show |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:3:5 | index |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:5:3:6:5 | show |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:10:5 | index |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:12:3:13:5 | show |
|
||||
| app/config/routes.rb:7:5:7:37 | call to post | app/controllers/posts_controller.rb:8:3:9:5 | upvote |
|
||||
| app/config/routes.rb:27:3:27:48 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:28:3:28:50 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
class UsersController < ActionController::Base
|
||||
def index
|
||||
request.params
|
||||
request.parameters
|
||||
request.GET
|
||||
request.POST
|
||||
request.query_parameters
|
||||
request.request_parameters
|
||||
request.filtered_parameters
|
||||
|
||||
request.authorization
|
||||
request.script_name
|
||||
request.path_info
|
||||
request.user_agent
|
||||
request.referer
|
||||
request.referrer
|
||||
request.host_authority
|
||||
request.content_type
|
||||
request.host
|
||||
request.hostname
|
||||
request.accept_encoding
|
||||
request.accept_language
|
||||
request.if_none_match
|
||||
request.if_none_match_etags
|
||||
request.content_mime_type
|
||||
|
||||
request.authority
|
||||
request.host
|
||||
request.host_authority
|
||||
request.host_with_port
|
||||
request.hostname
|
||||
request.forwarded_for
|
||||
request.forwarded_host
|
||||
request.port
|
||||
request.forwarded_port
|
||||
|
||||
request.media_type
|
||||
request.media_type_params
|
||||
request.content_charset
|
||||
request.base_url
|
||||
|
||||
request.body
|
||||
request.raw_post
|
||||
|
||||
request.env["HTTP_ACCEPT"]
|
||||
request.env["NOT_USER_CONTROLLED"]
|
||||
request.filtered_env["HTTP_ACCEPT"]
|
||||
request.filtered_env["NOT_USER_CONTROLLED"]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class MyMailer < ActionMailer::Base
|
||||
def foo
|
||||
sink params[:foo] # $hasTaintFlow
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
failures
|
||||
edges
|
||||
| mailer.rb:3:10:3:15 | call to params : | mailer.rb:3:10:3:21 | ...[...] |
|
||||
nodes
|
||||
| mailer.rb:3:10:3:15 | call to params : | semmle.label | call to params : |
|
||||
| mailer.rb:3:10:3:21 | ...[...] | semmle.label | ...[...] |
|
||||
subpaths
|
||||
#select
|
||||
| mailer.rb:3:10:3:21 | ...[...] | mailer.rb:3:10:3:15 | call to params : | mailer.rb:3:10:3:21 | ...[...] | $@ | mailer.rb:3:10:3:15 | call to params : | call to params : |
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @kind path-problem
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import TestUtilities.InlineFlowTest
|
||||
import PathGraph
|
||||
import codeql.ruby.frameworks.Rails
|
||||
|
||||
class ParamsTaintFlowConf extends DefaultTaintFlowConf {
|
||||
override predicate isSource(DataFlow::Node n) {
|
||||
n.asExpr().getExpr() instanceof Rails::ParamsCall
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, ParamsTaintFlowConf conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "$@", source, source.toString()
|
||||
@@ -136,6 +136,14 @@ edges
|
||||
| active_support.rb:191:34:191:34 | a : | active_support.rb:191:7:191:35 | call to new : |
|
||||
| active_support.rb:192:7:192:7 | x : | active_support.rb:192:7:192:16 | call to to_param : |
|
||||
| active_support.rb:192:7:192:16 | call to to_param : | active_support.rb:193:8:193:8 | y |
|
||||
| active_support.rb:197:7:197:16 | call to source : | active_support.rb:198:20:198:20 | a : |
|
||||
| active_support.rb:198:7:198:21 | call to new : | active_support.rb:199:7:199:7 | x : |
|
||||
| active_support.rb:198:20:198:20 | a : | active_support.rb:198:7:198:21 | call to new : |
|
||||
| active_support.rb:199:7:199:7 | x : | active_support.rb:199:7:199:17 | call to existence : |
|
||||
| active_support.rb:199:7:199:17 | call to existence : | active_support.rb:200:8:200:8 | y |
|
||||
| active_support.rb:199:7:199:17 | call to existence : | active_support.rb:201:7:201:7 | y : |
|
||||
| active_support.rb:201:7:201:7 | y : | active_support.rb:201:7:201:17 | call to existence : |
|
||||
| active_support.rb:201:7:201:17 | call to existence : | active_support.rb:202:8:202:8 | z |
|
||||
nodes
|
||||
| active_support.rb:9:9:9:18 | call to source : | semmle.label | call to source : |
|
||||
| active_support.rb:10:10:10:10 | x : | semmle.label | x : |
|
||||
@@ -310,6 +318,15 @@ nodes
|
||||
| active_support.rb:192:7:192:7 | x : | semmle.label | x : |
|
||||
| active_support.rb:192:7:192:16 | call to to_param : | semmle.label | call to to_param : |
|
||||
| active_support.rb:193:8:193:8 | y | semmle.label | y |
|
||||
| active_support.rb:197:7:197:16 | call to source : | semmle.label | call to source : |
|
||||
| active_support.rb:198:7:198:21 | call to new : | semmle.label | call to new : |
|
||||
| active_support.rb:198:20:198:20 | a : | semmle.label | a : |
|
||||
| active_support.rb:199:7:199:7 | x : | semmle.label | x : |
|
||||
| active_support.rb:199:7:199:17 | call to existence : | semmle.label | call to existence : |
|
||||
| active_support.rb:200:8:200:8 | y | semmle.label | y |
|
||||
| active_support.rb:201:7:201:7 | y : | semmle.label | y : |
|
||||
| active_support.rb:201:7:201:17 | call to existence : | semmle.label | call to existence : |
|
||||
| active_support.rb:202:8:202:8 | z | semmle.label | z |
|
||||
subpaths
|
||||
#select
|
||||
| active_support.rb:106:10:106:13 | ...[...] | active_support.rb:104:10:104:17 | call to source : | active_support.rb:106:10:106:13 | ...[...] | $@ | active_support.rb:104:10:104:17 | call to source : | call to source : |
|
||||
|
||||
@@ -192,3 +192,12 @@ def m_safe_buffer_to_param
|
||||
y = x.to_param
|
||||
sink y # $hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_pathname_existence
|
||||
a = source "a"
|
||||
x = Pathname.new(a)
|
||||
y = x.existence
|
||||
sink y # $hasTaintFlow=a
|
||||
z = y.existence
|
||||
sink z # $hasTaintFlow=a
|
||||
end
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
class CommentsController < ApplicationController
|
||||
def index
|
||||
request.params
|
||||
request.parameters
|
||||
request.GET
|
||||
request.POST
|
||||
request.query_parameters
|
||||
request.request_parameters
|
||||
request.filtered_parameters
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -120,6 +120,13 @@ hello.rb:
|
||||
# 18| HelloWorld
|
||||
#-----| super -> Greeting
|
||||
|
||||
instance_fields.rb:
|
||||
# 11| A_target
|
||||
#-----| super -> Object
|
||||
|
||||
# 26| B_target
|
||||
#-----| super -> Object
|
||||
|
||||
modules.rb:
|
||||
# 1| Empty
|
||||
|
||||
|
||||
@@ -219,6 +219,10 @@ getTarget
|
||||
| hello.rb:14:16:14:20 | call to hello | hello.rb:2:5:4:7 | hello |
|
||||
| hello.rb:20:16:20:20 | call to super | hello.rb:13:5:15:7 | message |
|
||||
| hello.rb:20:30:20:34 | call to world | hello.rb:5:5:7:7 | world |
|
||||
| instance_fields.rb:4:22:4:35 | call to new | calls.rb:117:5:117:16 | new |
|
||||
| instance_fields.rb:7:13:7:25 | call to target | instance_fields.rb:12:5:13:7 | target |
|
||||
| instance_fields.rb:19:22:19:35 | call to new | calls.rb:117:5:117:16 | new |
|
||||
| instance_fields.rb:22:13:22:25 | call to target | instance_fields.rb:27:5:28:7 | target |
|
||||
| modules.rb:12:5:12:26 | call to puts | calls.rb:102:5:102:30 | puts |
|
||||
| modules.rb:22:3:22:19 | call to puts | calls.rb:102:5:102:30 | puts |
|
||||
| modules.rb:33:3:33:25 | call to puts | calls.rb:102:5:102:30 | puts |
|
||||
@@ -435,6 +439,12 @@ publicMethod
|
||||
| hello.rb:5:5:7:7 | world |
|
||||
| hello.rb:13:5:15:7 | message |
|
||||
| hello.rb:19:5:21:7 | message |
|
||||
| instance_fields.rb:3:9:5:11 | create |
|
||||
| instance_fields.rb:6:9:8:11 | use |
|
||||
| instance_fields.rb:12:5:13:7 | target |
|
||||
| instance_fields.rb:18:9:20:11 | create |
|
||||
| instance_fields.rb:21:9:23:11 | use |
|
||||
| instance_fields.rb:27:5:28:7 | target |
|
||||
| modules.rb:9:5:10:7 | method_in_foo_bar |
|
||||
| modules.rb:16:3:17:5 | method_in_foo |
|
||||
| modules.rb:27:3:28:5 | method_in_another_definition_of_foo |
|
||||
|
||||
29
ruby/ql/test/library-tests/modules/instance_fields.rb
Normal file
29
ruby/ql/test/library-tests/modules/instance_fields.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
class A
|
||||
class << self
|
||||
def create
|
||||
@field = ::A_target.new
|
||||
end
|
||||
def use
|
||||
@field.target
|
||||
end
|
||||
end
|
||||
end
|
||||
class A_target
|
||||
def target
|
||||
end
|
||||
end
|
||||
|
||||
class B
|
||||
class << self
|
||||
def create
|
||||
@field = ::B_target.new
|
||||
end
|
||||
def use
|
||||
@field.target
|
||||
end
|
||||
end
|
||||
end
|
||||
class B_target
|
||||
def target
|
||||
end
|
||||
end
|
||||
@@ -47,6 +47,8 @@ getMethod
|
||||
| hello.rb:1:1:8:3 | EnglishWords | world | hello.rb:5:5:7:7 | world |
|
||||
| hello.rb:11:1:16:3 | Greeting | message | hello.rb:13:5:15:7 | message |
|
||||
| hello.rb:18:1:22:3 | HelloWorld | message | hello.rb:19:5:21:7 | message |
|
||||
| instance_fields.rb:11:1:14:3 | A_target | target | instance_fields.rb:12:5:13:7 | target |
|
||||
| instance_fields.rb:26:1:29:3 | B_target | target | instance_fields.rb:27:5:28:7 | target |
|
||||
| modules.rb:4:1:24:3 | Foo | method_in_another_definition_of_foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo |
|
||||
| modules.rb:4:1:24:3 | Foo | method_in_foo | modules.rb:16:3:17:5 | method_in_foo |
|
||||
| modules.rb:5:3:14:5 | Foo::Bar | method_in_another_definition_of_foo_bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar |
|
||||
@@ -430,6 +432,14 @@ lookupMethod
|
||||
| hello.rb:18:1:22:3 | HelloWorld | puts | calls.rb:102:5:102:30 | puts |
|
||||
| hello.rb:18:1:22:3 | HelloWorld | to_s | calls.rb:172:5:173:7 | to_s |
|
||||
| hello.rb:18:1:22:3 | HelloWorld | world | hello.rb:5:5:7:7 | world |
|
||||
| instance_fields.rb:11:1:14:3 | A_target | new | calls.rb:117:5:117:16 | new |
|
||||
| instance_fields.rb:11:1:14:3 | A_target | puts | calls.rb:102:5:102:30 | puts |
|
||||
| instance_fields.rb:11:1:14:3 | A_target | target | instance_fields.rb:12:5:13:7 | target |
|
||||
| instance_fields.rb:11:1:14:3 | A_target | to_s | calls.rb:172:5:173:7 | to_s |
|
||||
| instance_fields.rb:26:1:29:3 | B_target | new | calls.rb:117:5:117:16 | new |
|
||||
| instance_fields.rb:26:1:29:3 | B_target | puts | calls.rb:102:5:102:30 | puts |
|
||||
| instance_fields.rb:26:1:29:3 | B_target | target | instance_fields.rb:27:5:28:7 | target |
|
||||
| instance_fields.rb:26:1:29:3 | B_target | to_s | calls.rb:172:5:173:7 | to_s |
|
||||
| modules.rb:4:1:24:3 | Foo | method_in_another_definition_of_foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo |
|
||||
| modules.rb:4:1:24:3 | Foo | method_in_foo | modules.rb:16:3:17:5 | method_in_foo |
|
||||
| modules.rb:5:3:14:5 | Foo::Bar | method_in_another_definition_of_foo_bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar |
|
||||
@@ -852,6 +862,22 @@ enclosingMethod
|
||||
| hello.rb:20:30:20:34 | self | hello.rb:19:5:21:7 | message |
|
||||
| hello.rb:20:38:20:40 | "!" | hello.rb:19:5:21:7 | message |
|
||||
| hello.rb:20:39:20:39 | ! | hello.rb:19:5:21:7 | message |
|
||||
| instance_fields.rb:4:13:4:18 | @field | instance_fields.rb:3:9:5:11 | create |
|
||||
| instance_fields.rb:4:13:4:18 | self | instance_fields.rb:3:9:5:11 | create |
|
||||
| instance_fields.rb:4:13:4:35 | ... = ... | instance_fields.rb:3:9:5:11 | create |
|
||||
| instance_fields.rb:4:22:4:31 | A_target | instance_fields.rb:3:9:5:11 | create |
|
||||
| instance_fields.rb:4:22:4:35 | call to new | instance_fields.rb:3:9:5:11 | create |
|
||||
| instance_fields.rb:7:13:7:18 | @field | instance_fields.rb:6:9:8:11 | use |
|
||||
| instance_fields.rb:7:13:7:18 | self | instance_fields.rb:6:9:8:11 | use |
|
||||
| instance_fields.rb:7:13:7:25 | call to target | instance_fields.rb:6:9:8:11 | use |
|
||||
| instance_fields.rb:19:13:19:18 | @field | instance_fields.rb:18:9:20:11 | create |
|
||||
| instance_fields.rb:19:13:19:18 | self | instance_fields.rb:18:9:20:11 | create |
|
||||
| instance_fields.rb:19:13:19:35 | ... = ... | instance_fields.rb:18:9:20:11 | create |
|
||||
| instance_fields.rb:19:22:19:31 | B_target | instance_fields.rb:18:9:20:11 | create |
|
||||
| instance_fields.rb:19:22:19:35 | call to new | instance_fields.rb:18:9:20:11 | create |
|
||||
| instance_fields.rb:22:13:22:18 | @field | instance_fields.rb:21:9:23:11 | use |
|
||||
| instance_fields.rb:22:13:22:18 | self | instance_fields.rb:21:9:23:11 | use |
|
||||
| instance_fields.rb:22:13:22:25 | call to target | instance_fields.rb:21:9:23:11 | use |
|
||||
| private.rb:84:7:84:32 | call to puts | private.rb:83:11:85:5 | m1 |
|
||||
| private.rb:84:7:84:32 | self | private.rb:83:11:85:5 | m1 |
|
||||
| private.rb:84:12:84:32 | "PrivateOverride1#m1" | private.rb:83:11:85:5 | m1 |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user