diff --git a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll index 3bb1c86eaea..37eefd44c30 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll @@ -210,233 +210,243 @@ private module Mongoose { } /** - * Gets a data flow node referring to a Mongoose model object. + * Provides classes modeling the Mongoose Model class */ - private DataFlow::SourceNode getAModel(DataFlow::TypeTracker t) { - ( - result = getAMongooseInstance().getAMemberCall("model") or - result.hasUnderlyingType("mongoose", "Model") - ) and - t.start() - or - exists(DataFlow::TypeTracker t2 | result = getAModel(t2).track(t2, t)) - } - - /** - * Gets a data flow node referring to a Mongoose model object. - */ - DataFlow::SourceNode getAModel() { result = getAModel(DataFlow::TypeTracker::end()) } - - /** - * Provides signatures for the Model methods. - */ - module ModelMethodSignatures { + module Model { /** - * Holds if Model method `name` interprets parameter `n` as a query. + * Gets a data flow node referring to a Mongoose Model object. */ - 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 signatures for the Query methods. - */ - module QueryMethodSignatures { - /** - * Holds if Query method `name` interprets parameter `n` as a query. - */ - predicate interpretsArgumentAsQuery(string name, int n) { - n = 0 and + private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { ( - name = "and" or + 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 = "elemMatch" 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 = "merge" or - name = "nor" or - name = "or" or - name = "remove" or + name = "geosearch" 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 + } + } + } + + /** + * 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) { ( - name = "distinct" or - name = "findOneAndUpdate" or - name = "update" or - name = "updateMany" or - name = "updateOne" + 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() ) } /** - * Holds if Query method `name` returns a Query. + * Gets a data flow node referring to a Mongoose query object. */ - 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" + 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" + } } } - /** - * A Mongoose query object as a result of a Model method call. - */ - private class QueryFromModel extends DataFlow::MethodCallNode { - QueryFromModel() { - exists(string name | - ModelMethodSignatures::returnsQuery(name) and - getAModel().getAMethodCall(name) = this - ) - } - } - - /** - * A Mongoose query object as a result of a Query constructor invocation. - */ - private 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 getAQuery(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 = getAQuery(t2) | - result = succ.track(t2, t) - or - result = succ.getAMethodCall(any(string name | QueryMethodSignatures::returnsQuery(name))) and - t = t2.continue() - ) - } - - /** - * Gets a data flow node referring to a Mongoose query object. - */ - private DataFlow::SourceNode getAQuery() { result = getAQuery(DataFlow::TypeTracker::end()) } - /** * An expression passed to `mongoose.createConnection` to supply credentials. */ @@ -460,15 +470,15 @@ private module Mongoose { class MongoDBQueryPart extends NoSQL::Query { MongoDBQueryPart() { exists(DataFlow::MethodCallNode mcn, string method, int n | - ModelMethodSignatures::interpretsArgumentAsQuery(method, n) and - mcn = getAModel().getAMethodCall(method) and + Model::MethodSignatures::interpretsArgumentAsQuery(method, n) and + mcn = Model::ref().getAMethodCall(method) and this = mcn.getArgument(n).asExpr() ) or - this = any(QueryFromConstructor c).getArgument(2).asExpr() + this = any(Query::QueryFromConstructor c).getArgument(2).asExpr() or - exists(string method, int n | QueryMethodSignatures::interpretsArgumentAsQuery(method, n) | - this = getAQuery().getAMethodCall(method).getArgument(n).asExpr() + exists(string method, int n | Query::MethodSignatures::interpretsArgumentAsQuery(method, n) | + this = Query::ref().getAMethodCall(method).getArgument(n).asExpr() ) } } @@ -483,13 +493,13 @@ private module Mongoose { this = mcn and ( exists(string method | - ModelMethodSignatures::returnsQuery(method) and - mcn = getAModel().getAMethodCall(method) and + 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 - getAQuery().getAMethodCall() = mcn and + Query::ref().getAMethodCall() = mcn and ( // explicit execution using a Query method call exists(string executor | executor = "exec" or executor = "then" or executor = "catch" |