Merge branch 'master' of git.semmle.com:Semmle/ql into CustomTrack

This commit is contained in:
Erik Krogh Kristensen
2020-03-14 14:37:52 +01:00
186 changed files with 3391 additions and 2191 deletions

View File

@@ -64,7 +64,10 @@ class RedundantIdemnecantOperand extends RedundantOperand {
* arguments to integers. For example, `x&x` is a common idiom for converting `x` to an integer.
*/
class RedundantIdempotentOperand extends RedundantOperand {
RedundantIdempotentOperand() { getParent() instanceof LogicalBinaryExpr }
RedundantIdempotentOperand() {
getParent() instanceof LogicalBinaryExpr and
not exists(UpdateExpr e | e.getParentExpr+() = this)
}
}
/**

View File

@@ -72,6 +72,9 @@ predicate benignContext(Expr e) {
or
// arguments to Promise.resolve (and promise library variants) are benign.
e = any(PromiseCreationCall promise).getValue().asExpr()
or
// arguments to other (unknown) promise creations.
e = any(DataFlow::CallNode call | call.getCalleeName() = "resolve").getAnArgument().asExpr()
}
predicate oneshotClosure(DataFlow::CallNode call) {
@@ -153,56 +156,6 @@ predicate hasNonVoidReturnType(Function f) {
exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | not type.isVoid())
}
/**
* Provides classes for working with various Deferred implementations.
* It is a heuristic. The heuristic assume that a class is a promise defintion
* if the class is called "Deferred" and the method `resolve` is called on an instance.
*
* Removes some false positives in the js/use-of-returnless-function query.
*/
module Deferred {
/**
* An instance of a `Deferred` class.
* For example the result from `new Deferred()` or `new $.Deferred()`.
*/
class DeferredInstance extends DataFlow::NewNode {
// Describes both `new Deferred()`, `new $.Deferred` and other variants.
DeferredInstance() { this.getCalleeName() = "Deferred" }
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
}
/**
* A promise object created by a Deferred constructor
*/
private class DeferredPromiseDefinition extends PromiseDefinition, DeferredInstance {
DeferredPromiseDefinition() {
// hardening of the "Deferred" heuristic: a method call to `resolve`.
exists(ref().getAMethodCall("resolve"))
}
override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
}
/**
* A resolved promise created by a `new Deferred().resolve()` call.
*/
class ResolvedDeferredPromiseDefinition extends PromiseCreationCall {
ResolvedDeferredPromiseDefinition() {
this = any(DeferredPromiseDefinition def).ref().getAMethodCall("resolve")
}
override DataFlow::Node getValue() { result = getArgument(0) }
}
}
from DataFlow::CallNode call, Function func, string name, string msg
where
(

View File

@@ -93,6 +93,12 @@ module CharacterEscapes {
// conservative formulation: we do not know in general if the sequence is enclosed in a character class `[...]`
result = Sets::regexpMetaChars().charAt(_) and
mistake = "may still represent a meta-character"
) and
// avoid the benign case where preceding escaped backslashes turns into backslashes when the regexp is constructed
not exists(string raw |
not rawStringNode instanceof RegExpLiteral and
hasRawStringAndQuote(_, _, rawStringNode, raw) and
result = raw.regexpFind("(?<=(^|[^\\\\])((\\\\{3})|(\\\\{7}))).", _, i)
)
}
}

View File

@@ -149,7 +149,7 @@ abstract class Configuration extends string {
predicate isBarrier(DataFlow::Node node) {
exists(BarrierGuardNode guard |
isBarrierGuardInternal(guard) and
guard.internalBlocks(node, "")
barrierGuardBlocksNode(guard, node, "")
)
}
@@ -183,7 +183,7 @@ abstract class Configuration extends string {
predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) {
exists(BarrierGuardNode guard |
isBarrierGuardInternal(guard) and
guard.internalBlocks(node, lbl)
barrierGuardBlocksNode(guard, node, lbl)
)
or
none() // relax type inference to account for overriding
@@ -199,6 +199,10 @@ abstract class Configuration extends string {
*/
predicate isBarrierGuard(BarrierGuardNode guard) { none() }
/**
* Holds if `guard` is a barrier guard for this configuration, added through
* `isBarrierGuard` or `AdditionalBarrierGuardNode`.
*/
private predicate isBarrierGuardInternal(BarrierGuardNode guard) {
isBarrierGuard(guard)
or
@@ -319,44 +323,6 @@ module FlowLabel {
* implementations of `blocks` will _both_ apply to any configuration that includes either of them.
*/
abstract class BarrierGuardNode extends DataFlow::Node {
/**
* Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing
* through an access path.
*
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
*
* INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`.
*/
predicate internalBlocks(DataFlow::Node nd, string label) {
// 1) `nd` is a use of a refinement node that blocks its input variable
exists(SsaRefinementNode ref, boolean outcome |
nd = DataFlow::ssaDefinitionNode(ref) and
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
ssaRefinementBlocks(outcome, ref, label)
)
or
// 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p`
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
getEnclosingExpr() = cond.getTest() and
outcome = cond.getOutcome() and
barrierGuardBlocksAccessPath(this, outcome, p, label) and
cond.dominates(bb)
)
}
/**
* Holds if there exists an input variable of `ref` that blocks the label `label`.
*
* This predicate is outlined to give the optimizer a hint about the join ordering.
*/
private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) {
getEnclosingExpr() = ref.getGuard().getTest() and
forex(SsaVariable input | input = ref.getAnInput() |
barrierGuardBlocksExpr(this, outcome, input.getAUse(), label)
)
}
/**
* Holds if this node blocks expression `e` provided it evaluates to `outcome`.
*
@@ -387,6 +353,17 @@ private predicate barrierGuardBlocksExpr(
guard.(AdditionalBarrierGuardCall).internalBlocksLabel(outcome, test, label)
}
/**
* Holds if `guard` blocks the flow of a value reachable through exploratory flow.
*/
pragma[noinline]
private predicate barrierGuardIsRelevant(BarrierGuardNode guard) {
exists(Expr e |
barrierGuardBlocksExpr(guard, _, e, _) and
isRelevantForward(e.flow(), _)
)
}
/**
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
* an access path.
@@ -397,9 +374,50 @@ pragma[noinline]
private predicate barrierGuardBlocksAccessPath(
BarrierGuardNode guard, boolean outcome, AccessPath ap, string label
) {
barrierGuardIsRelevant(guard) and
barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label)
}
/**
* Holds if there exists an input variable of `ref` that blocks the label `label`.
*
* This predicate is outlined to give the optimizer a hint about the join ordering.
*/
private predicate barrierGuardBlocksSsaRefinement(
BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label
) {
barrierGuardIsRelevant(guard) and
guard.getEnclosingExpr() = ref.getGuard().getTest() and
forex(SsaVariable input | input = ref.getAnInput() |
barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label)
)
}
/**
* Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing
* through an access path.
*
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
*/
private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node nd, string label) {
// 1) `nd` is a use of a refinement node that blocks its input variable
exists(SsaRefinementNode ref, boolean outcome |
nd = DataFlow::ssaDefinitionNode(ref) and
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
barrierGuardBlocksSsaRefinement(guard, outcome, ref, label)
)
or
// 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p`
barrierGuardIsRelevant(guard) and
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
guard.getEnclosingExpr() = cond.getTest() and
outcome = cond.getOutcome() and
barrierGuardBlocksAccessPath(guard, outcome, p, label) and
cond.dominates(bb)
)
}
/**
* Holds if `guard` should block flow along the edge `pred -> succ`.
*
@@ -408,6 +426,7 @@ private predicate barrierGuardBlocksAccessPath(
private predicate barrierGuardBlocksEdge(
BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label
) {
barrierGuardIsRelevant(guard) and
exists(
SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome
|
@@ -569,11 +588,12 @@ private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueN
/**
* Holds if there is a flow step from `pred` to `succ` described by `summary`
* under configuration `cfg`.
* under configuration `cfg`, disregarding barriers.
*
* Summary steps through function calls are not taken into account.
*/
private predicate basicFlowStep(
pragma[inline]
private predicate basicFlowStepNoBarrier(
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg
) {
isLive() and
@@ -582,8 +602,7 @@ private predicate basicFlowStep(
// Local flow
exists(FlowLabel predlbl, FlowLabel succlbl |
localFlowStep(pred, succ, cfg, predlbl, succlbl) and
not isLabeledBarrierEdge(cfg, pred, succ, predlbl) and
not isBarrierEdge(cfg, pred, succ) and
not cfg.isBarrierEdge(pred, succ) and
summary = MkPathSummary(false, false, predlbl, succlbl)
)
or
@@ -605,6 +624,20 @@ private predicate basicFlowStep(
)
}
/**
* Holds if there is a flow step from `pred` to `succ` described by `summary`
* under configuration `cfg`.
*
* Summary steps through function calls are not taken into account.
*/
private predicate basicFlowStep(
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg
) {
basicFlowStepNoBarrier(pred, succ, summary, cfg) and
not isLabeledBarrierEdge(cfg, pred, succ, summary.getStartLabel()) and
not isBarrierEdge(cfg, pred, succ)
}
/**
* Holds if there is a flow step from `pred` to `succ` under configuration `cfg`,
* including both basic flow steps and steps into/out of properties.
@@ -615,7 +648,7 @@ private predicate basicFlowStep(
private predicate exploratoryFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
) {
basicFlowStep(pred, succ, _, cfg) or
basicFlowStepNoBarrier(pred, succ, _, cfg) or
basicStoreStep(pred, succ, _) or
basicLoadStep(pred, succ, _) or
isAdditionalStoreStep(pred, succ, _, cfg) or
@@ -1442,6 +1475,7 @@ private class BarrierGuardFunction extends Function {
string label;
BarrierGuardFunction() {
barrierGuardIsRelevant(guard) and
exists(Expr e |
exists(Expr returnExpr |
returnExpr = guard.asExpr()

View File

@@ -697,14 +697,14 @@ abstract private class CallWithAnalyzedParameters extends FunctionWithAnalyzedPa
}
override predicate mayReceiveArgument(Parameter p) {
exists(DataFlow::InvokeNode invk, int argIdx |
invk = getAnInvocation() and
p = getParameter(argIdx)
|
exists(invk.getArgument(argIdx))
or
invk.asExpr().(InvokeExpr).isSpreadArgument([0 .. argIdx])
exists(int argIdx |
p = getParameter(argIdx) and
getAnInvocation().getNumArgument() > argIdx
)
or
// All parameters may receive an argument if invoked with a spread argument
p = getAParameter() and
getAnInvocation().asExpr().(InvokeExpr).isSpreadArgument(_)
}
}

View File

@@ -23,7 +23,15 @@ private module MongoDB {
*/
private DataFlow::SourceNode getAMongoClient(DataFlow::TypeTracker t) {
t.start() and
result = mongodb().getAPropertyRead("MongoClient")
(
result = mongodb().getAPropertyRead("MongoClient")
or
exists(DataFlow::ParameterNode p |
p = result and
p = getAMongoDbCallback().getParameter(1) and
not p.getName().toLowerCase() = "db" // mongodb v2 provides a `Db` here
)
)
or
exists(DataFlow::TypeTracker t2 | result = getAMongoClient(t2).track(t2, t))
}
@@ -36,7 +44,7 @@ private module MongoDB {
/** Gets a data flow node that leads to a `connect` callback. */
private DataFlow::SourceNode getAMongoDbCallback(DataFlow::TypeBackTracker t) {
t.start() and
result = getAMongoClient().getAMemberCall("connect").getArgument(1).getALocalSource()
result = getAMongoClient().getAMemberCall("connect").getLastArgument().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = getAMongoDbCallback(t2).backtrack(t2, t))
}
@@ -51,7 +59,15 @@ private module MongoDB {
*/
private DataFlow::SourceNode getAMongoDb(DataFlow::TypeTracker t) {
t.start() and
result = getAMongoDbCallback().getParameter(1)
(
exists(DataFlow::ParameterNode p |
p = result and
p = getAMongoDbCallback().getParameter(1) and
not p.getName().toLowerCase() = "client" // mongodb v3 provides a `Mongoclient` here
)
or
result = getAMongoClient().getAMethodCall("db")
)
or
exists(DataFlow::TypeTracker t2 | result = getAMongoDb(t2).track(t2, t))
}
@@ -104,43 +120,71 @@ private module MongoDB {
QueryCall() {
exists(string m | this = getACollection().getAMethodCall(m) |
m = "aggregate" and queryArgIdx = 0
or
m = "count" and queryArgIdx = 0
or
m = "deleteMany" and queryArgIdx = 0
or
m = "deleteOne" and queryArgIdx = 0
or
m = "distinct" and queryArgIdx = 1
or
m = "find" and queryArgIdx = 0
or
m = "findOne" and queryArgIdx = 0
or
m = "findOneAndDelete" and queryArgIdx = 0
or
m = "findOneAndRemove" and queryArgIdx = 0
or
m = "findOneAndDelete" and queryArgIdx = 0
or
m = "findOneAndUpdate" and queryArgIdx = 0
or
m = "remove" and queryArgIdx = 0
or
m = "replaceOne" and queryArgIdx = 0
or
m = "update" and queryArgIdx = 0
or
m = "updateMany" and queryArgIdx = 0
or
m = "updateOne" and queryArgIdx = 0
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
)
}
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
}
/**
* Provides signatures for the Collection methods.
*/
module CollectionMethodSignatures {
/**
* Holds if Collection method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
// FilterQuery
(
name = "aggregate" and n = 0
or
name = "count" and n = 0
or
name = "countDocuments" and n = 0
or
name = "deleteMany" and n = 0
or
name = "deleteOne" and n = 0
or
name = "distinct" and n = 1
or
name = "find" and n = 0
or
name = "findOne" and n = 0
or
name = "findOneAndDelete" and n = 0
or
name = "findOneAndRemove" and n = 0
or
name = "findOneAndReplace" and n = 0
or
name = "findOneAndUpdate" and n = 0
or
name = "remove" and n = 0
or
name = "replaceOne" and n = 0
or
name = "update" and n = 0
or
name = "updateMany" and n = 0
or
name = "updateOne" and n = 0
)
or
// UpdateQuery
(
name = "findOneAndUpdate" and n = 1
or
name = "update" and n = 1
or
name = "updateMany" and n = 1
or
name = "updateOne" and n = 1
)
}
}
/**
* An expression that is interpreted as a MongoDB query.
*/
@@ -166,10 +210,241 @@ private module Mongoose {
}
/**
* A Mongoose collection object.
* Provides classes modeling the Mongoose Model class
*/
class Model extends MongoDB::Collection {
Model() { this = getAMongooseInstance().getAMemberCall("model") }
module Model {
/**
* Gets a data flow node referring to a Mongoose Model object.
*/
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
(
result = getAMongooseInstance().getAMemberCall("model") or
result.hasUnderlyingType("mongoose", "Model")
) and
t.start()
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
/**
* Gets a data flow node referring to a Mongoose model object.
*/
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
/**
* Provides signatures for the Model methods.
*/
module MethodSignatures {
/**
* Holds if Model method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
// implement lots of the MongoDB collection interface
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(name, n)
or
name = "findByIdAndUpdate" and n = 1
}
/**
* Holds if Model method `name` returns a Query.
*/
predicate returnsQuery(string name) {
name = "$where" or
name = "count" or
name = "countDocuments" or
name = "deleteMany" or
name = "deleteOne" or
name = "find" or
name = "findById" or
name = "findByIdAndDelete" or
name = "findByIdAndRemove" or
name = "findByIdAndUpdate" or
name = "findOne" or
name = "findOneAndDelete" or
name = "findOneAndRemove" or
name = "findOneAndReplace" or
name = "findOneAndUpdate" or
name = "geosearch" or
name = "replaceOne" or
name = "update" or
name = "updateMany" or
name = "updateOne" or
name = "where"
}
}
}
/**
* Provides classes modeling the Mongoose Query class
*/
module Query {
/**
* A Mongoose query object as a result of a Model method call.
*/
private class QueryFromModel extends DataFlow::MethodCallNode {
QueryFromModel() {
exists(string name |
Model::MethodSignatures::returnsQuery(name) and
Model::ref().getAMethodCall(name) = this
)
}
}
/**
* A Mongoose query object as a result of a Query constructor invocation.
*/
class QueryFromConstructor extends DataFlow::NewNode {
QueryFromConstructor() {
this = getAMongooseInstance().getAPropertyRead("Query").getAnInstantiation()
}
}
/**
* Gets a data flow node referring to a Mongoose query object.
*/
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
(
result instanceof QueryFromConstructor or
result instanceof QueryFromModel or
result.hasUnderlyingType("mongoose", "Query")
) and
t.start()
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode succ | succ = ref(t2) |
result = succ.track(t2, t)
or
result = succ.getAMethodCall(any(string name | MethodSignatures::returnsQuery(name))) and
t = t2.continue()
)
}
/**
* Gets a data flow node referring to a Mongoose query object.
*/
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
/**
* Provides signatures for the Query methods.
*/
module MethodSignatures {
/**
* Holds if Query method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
n = 0 and
(
name = "and" or
name = "count" or
name = "countDocuments" or
name = "deleteMany" or
name = "deleteOne" or
name = "elemMatch" or
name = "find" or
name = "findOne" or
name = "findOneAndDelete" or
name = "findOneAndRemove" or
name = "findOneAndReplace" or
name = "findOneAndUpdate" or
name = "merge" or
name = "nor" or
name = "or" or
name = "remove" or
name = "replaceOne" or
name = "setQuery" or
name = "setUpdate" or
name = "update" or
name = "updateMany" or
name = "updateOne" or
name = "where"
)
or
n = 1 and
(
name = "distinct" or
name = "findOneAndUpdate" or
name = "update" or
name = "updateMany" or
name = "updateOne"
)
}
/**
* Holds if Query method `name` returns a Query.
*/
predicate returnsQuery(string name) {
name = "$where" or
name = "J" or
name = "all" or
name = "and" or
name = "batchsize" or
name = "box" or
name = "center" or
name = "centerSphere" or
name = "circle" or
name = "collation" or
name = "comment" or
name = "count" or
name = "countDocuments" or
name = "distinct" or
name = "elemMatch" or
name = "equals" or
name = "error" or
name = "estimatedDocumentCount" or
name = "exists" or
name = "explain" or
name = "find" or
name = "findById" or
name = "findOne" or
name = "findOneAndRemove" or
name = "findOneAndUpdate" or
name = "geometry" or
name = "get" or
name = "gt" or
name = "gte" or
name = "hint" or
name = "in" or
name = "intersects" or
name = "lean" or
name = "limit" or
name = "lt" or
name = "lte" or
name = "map" or
name = "map" or
name = "maxDistance" or
name = "maxTimeMS" or
name = "maxscan" or
name = "mod" or
name = "ne" or
name = "near" or
name = "nearSphere" or
name = "nin" or
name = "or" or
name = "orFail" or
name = "polygon" or
name = "populate" or
name = "read" or
name = "readConcern" or
name = "regexp" or
name = "remove" or
name = "select" or
name = "session" or
name = "set" or
name = "setOptions" or
name = "setQuery" or
name = "setUpdate" or
name = "size" or
name = "skip" or
name = "slaveOk" or
name = "slice" or
name = "snapshot" or
name = "sort" or
name = "update" or
name = "w" or
name = "where" or
name = "within" or
name = "wtimeout"
}
}
}
/**
@@ -188,4 +463,58 @@ private module Mongoose {
override string getCredentialsKind() { result = kind }
}
/**
* An expression that is interpreted as a (part of a) MongoDB query.
*/
class MongoDBQueryPart extends NoSQL::Query {
MongoDBQueryPart() {
exists(DataFlow::MethodCallNode mcn, string method, int n |
Model::MethodSignatures::interpretsArgumentAsQuery(method, n) and
mcn = Model::ref().getAMethodCall(method) and
this = mcn.getArgument(n).asExpr()
)
or
this = any(Query::QueryFromConstructor c).getArgument(2).asExpr()
or
exists(string method, int n | Query::MethodSignatures::interpretsArgumentAsQuery(method, n) |
this = Query::ref().getAMethodCall(method).getArgument(n).asExpr()
)
}
}
/**
* An evaluation of a MongoDB query.
*/
class MongoDBQueryEvaluation extends DatabaseAccess {
DataFlow::MethodCallNode mcn;
MongoDBQueryEvaluation() {
this = mcn and
(
exists(string method |
Model::MethodSignatures::returnsQuery(method) and
mcn = Model::ref().getAMethodCall(method) and
// callback provided to a Model method call
exists(mcn.getCallback(mcn.getNumArgument() - 1))
)
or
Query::ref().getAMethodCall() = mcn and
(
// explicit execution using a Query method call
exists(string executor | executor = "exec" or executor = "then" or executor = "catch" |
mcn.getMethodName() = executor
)
or
// callback provided to a Query method call
exists(mcn.getCallback(mcn.getNumArgument() - 1))
)
)
}
override DataFlow::Node getAQueryArgument() {
// NB: this does not account for all of the chained calls leading to this execution
mcn.getAnArgument().asExpr().(MongoDBQueryPart).flow() = result
}
}
}