summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Suarez <kyle.suarez@mongodb.com>2017-03-13 12:16:34 -0400
committerKyle Suarez <kyle.suarez@mongodb.com>2017-03-13 12:16:45 -0400
commitd98143c5db73a8a9a49ef20e9e0f2732b4de5063 (patch)
treed413ea7a3eb37e24e78dfffeaec6ad9453ff6615
parent259fed1b72cf3145948068002bdb2cdb94b40d0d (diff)
downloadmongo-d98143c5db73a8a9a49ef20e9e0f2732b4de5063.tar.gz
SERVER-26703 reject commands exceeding the BSON depth limit
Any command sent to the server that exceeds the depth limit will fail. This also prevents users from inserting documents that exceed the depth limit.
-rw-r--r--jstests/core/nestedarr1.js60
-rw-r--r--jstests/core/nestedobj1.js62
-rw-r--r--jstests/noPassthrough/max_bson_depth_parameter.js28
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/bson/bson_depth.cpp43
-rw-r--r--src/mongo/bson/bson_depth.h56
-rw-r--r--src/mongo/bson/bson_validate.cpp7
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp57
-rw-r--r--src/mongo/db/matcher/expression_parser.h23
-rw-r--r--src/mongo/db/matcher/expression_parser_tree.cpp12
-rw-r--r--src/mongo/db/matcher/expression_parser_tree_test.cpp81
-rw-r--r--src/mongo/rpc/object_check.cpp26
12 files changed, 272 insertions, 184 deletions
diff --git a/jstests/core/nestedarr1.js b/jstests/core/nestedarr1.js
index 720c1c27821..239502202e3 100644
--- a/jstests/core/nestedarr1.js
+++ b/jstests/core/nestedarr1.js
@@ -1,29 +1,45 @@
-// make sure that we don't crash on large nested arrays but correctly do not index them
-// SERVER-5127, SERVER-5036
+/**
+ * Inserts documents with an indexed nested array field, progressively increasing the nesting
+ * depth until the key is too large to index. This tests that we support at least the minimum
+ * supported BSON nesting depth, as well as maintaining index consistency.
+ */
+(function() {
+ "use strict";
-function makeNestArr(depth) {
- if (depth == 1) {
- return {a: [depth]};
- } else {
- return {a: [makeNestArr(depth - 1)]};
+ function makeNestArr(depth) {
+ if (depth == 1) {
+ return {a: 1};
+ } else if (depth == 2) {
+ return {a: [1]};
+ } else {
+ return {a: [makeNestArr(depth - 2)]};
+ }
}
-}
-t = db.arrNestTest;
-t.drop();
+ let collection = db.arrNestTest;
+ collection.drop();
-t.ensureIndex({a: 1});
+ assert.commandWorked(collection.ensureIndex({a: 1}));
-n = 1;
-while (true) {
- var before = t.count();
- t.insert({_id: n, a: makeNestArr(n)});
- var after = t.count();
- if (before == after)
- break;
- n++;
-}
+ const kMaxDocumentDepthSoftLimit = 100;
+ const kJavaScriptMaxDepthLimit = 150;
-assert(n > 30, "not enough n: " + n);
+ let level;
+ for (level = 1; level < kJavaScriptMaxDepthLimit - 3; level++) {
+ let res = db.runCommand({insert: collection.getName(), documents: [makeNestArr(level)]});
+ if (!res.ok) {
+ assert.commandFailedWithCode(
+ res, 17280, "Expected insertion to fail only because key is too large to index");
+ break;
+ }
+ }
-assert.eq(t.count(), t.find({_id: {$gt: 0}}).hint({a: 1}).itcount());
+ assert.gt(level,
+ kMaxDocumentDepthSoftLimit,
+ "Unable to insert a document nested with " + level +
+ " levels, which is less than the supported limit of " +
+ kMaxDocumentDepthSoftLimit);
+ assert.eq(collection.count(),
+ collection.find().hint({a: 1}).itcount(),
+ "Number of documents in collection does not match number of entries in index");
+}());
diff --git a/jstests/core/nestedobj1.js b/jstests/core/nestedobj1.js
index 379224c1775..3eb4b04dd1c 100644
--- a/jstests/core/nestedobj1.js
+++ b/jstests/core/nestedobj1.js
@@ -1,30 +1,44 @@
-// SERVER-5127, SERVER-5036
-
-function makeNestObj(depth) {
- toret = {a: 1};
-
- for (i = 1; i < depth; i++) {
- toret = {a: toret};
+/**
+ * Inserts documents with an indexed nested document field, progressively increasing the nesting
+ * depth until the key is too large to index. This tests that we support at least the minimum
+ * supported BSON nesting depth, as well as maintaining index consistency.
+ */
+(function() {
+ "use strict";
+
+ function makeNestObj(depth) {
+ if (depth == 1) {
+ return {a: 1};
+ } else {
+ return {a: makeNestObj(depth - 1)};
+ }
}
- return toret;
-}
+ let collection = db.objNestTest;
+ collection.drop();
-t = db.objNestTest;
-t.drop();
+ assert.commandWorked(collection.ensureIndex({a: 1}));
-t.ensureIndex({a: 1});
+ const kMaxDocumentDepthSoftLimit = 100;
+ const kJavaScriptMaxDepthLimit = 150;
-n = 1;
-while (true) {
- var before = t.count();
- t.insert({_id: n, a: makeNestObj(n)});
- var after = t.count();
- if (before == after)
- break;
- n++;
-}
-
-assert(n > 30, "not enough n: " + n);
+ let level;
+ for (level = 1; level < kJavaScriptMaxDepthLimit - 3; level++) {
+ let object = makeNestObj(level);
+ let res = db.runCommand({insert: collection.getName(), documents: [makeNestObj(level)]});
+ if (!res.ok) {
+ assert.commandFailedWithCode(
+ res, 17280, "Expected insertion to fail only because key is too large to index");
+ break;
+ }
+ }
-assert.eq(t.count(), t.find({_id: {$gt: 0}}).hint({a: 1}).itcount());
+ assert.gt(level,
+ kMaxDocumentDepthSoftLimit,
+ "Unable to insert a document nested with " + level +
+ " levels, which is less than the supported limit of " +
+ kMaxDocumentDepthSoftLimit);
+ assert.eq(collection.count(),
+ collection.find().hint({a: 1}).itcount(),
+ "Number of documents in collection does not match number of entries in index");
+}());
diff --git a/jstests/noPassthrough/max_bson_depth_parameter.js b/jstests/noPassthrough/max_bson_depth_parameter.js
new file mode 100644
index 00000000000..337205d3134
--- /dev/null
+++ b/jstests/noPassthrough/max_bson_depth_parameter.js
@@ -0,0 +1,28 @@
+/**
+ * Tests that the server properly respects the maxBSONDepth parameter, and will fail to start up if
+ * given an invalid depth.
+ */
+(function() {
+ "use strict";
+
+ const kTestName = "max_bson_depth_parameter";
+
+ // Start mongod with a valid BSON depth, then test that it accepts and rejects command
+ // appropriately based on the depth.
+ let conn = MongoRunner.runMongod({setParameter: "maxBSONDepth=5"});
+ assert.neq(null, conn, "Failed to start mongod");
+ let testDB = conn.getDB("test");
+ assert.commandWorked(testDB.runCommand({ping: 1}), "Failed to run a command on the server");
+ assert.commandFailedWithCode(
+ testDB.runCommand({find: "coll", filter: {x: {x: {x: {x: {x: {x: 1}}}}}}}),
+ ErrorCodes.Overflow,
+ "Expected server to reject command for exceeding the nesting depth limit");
+
+ // Restart mongod with a negative maximum BSON depth and test that it fails to start.
+ MongoRunner.stopMongod(conn);
+ conn = MongoRunner.runMongod({setParameter: "maxBSONDepth=-4"});
+ assert.eq(null, conn, "Expected mongod to fail at startup because depth was negative");
+
+ conn = MongoRunner.runMongod({setParameter: "maxBSONDepth=1"});
+ assert.eq(null, conn, "Expected mongod to fail at startup because depth was too low");
+}());
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 2adcef690ee..74f1627f9f7 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -68,6 +68,7 @@ baseSource=[
'base/string_data.cpp',
'base/validate_locale.cpp',
'bson/bson_comparator_interface_base.cpp',
+ 'bson/bson_depth.cpp',
'bson/bson_validate.cpp',
'bson/bsonelement.cpp',
'bson/bsonmisc.cpp',
diff --git a/src/mongo/bson/bson_depth.cpp b/src/mongo/bson/bson_depth.cpp
new file mode 100644
index 00000000000..153d53d85ef
--- /dev/null
+++ b/src/mongo/bson/bson_depth.cpp
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bson_depth.h"
+
+namespace mongo {
+constexpr std::int32_t BSONDepth::kDefaultMaxAllowableDepth;
+constexpr std::int32_t BSONDepth::kBSONDepthParameterFloor;
+constexpr std::int32_t BSONDepth::kBSONDepthParameterCeiling;
+
+std::int32_t BSONDepth::maxAllowableDepth = BSONDepth::kDefaultMaxAllowableDepth;
+
+std::uint32_t BSONDepth::getMaxAllowableDepth() {
+ return static_cast<std::uint32_t>(BSONDepth::maxAllowableDepth);
+}
+} // namespace mongo
diff --git a/src/mongo/bson/bson_depth.h b/src/mongo/bson/bson_depth.h
new file mode 100644
index 00000000000..ef8ffc3631d
--- /dev/null
+++ b/src/mongo/bson/bson_depth.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace mongo {
+/**
+ * Controls the maximum BSON depth tolerated by the server.
+ */
+struct BSONDepth {
+ // The default BSON depth nesting limit.
+ static constexpr std::int32_t kDefaultMaxAllowableDepth = 200;
+
+ // The minimum allowable value for the BSON depth parameter.
+ static constexpr std::int32_t kBSONDepthParameterFloor = 5;
+
+ // The maximum allowable value for the BSON depth parameter.
+ static constexpr std::int32_t kBSONDepthParameterCeiling = 1000;
+
+ // The depth of BSON accepted by the server. Configurable via the 'maxBSONDepth' server
+ // parameter.
+ static std::int32_t maxAllowableDepth;
+
+ /**
+ * Returns the maximum allowable BSON depth as an unsigned integer.
+ */
+ static std::uint32_t getMaxAllowableDepth();
+};
+} // namespace mongo
diff --git a/src/mongo/bson/bson_validate.cpp b/src/mongo/bson/bson_validate.cpp
index 8a11597a45e..c2d97a6496e 100644
--- a/src/mongo/bson/bson_validate.cpp
+++ b/src/mongo/bson/bson_validate.cpp
@@ -32,6 +32,7 @@
#include <vector>
#include "mongo/base/data_view.h"
+#include "mongo/bson/bson_depth.h"
#include "mongo/bson/bson_validate.h"
#include "mongo/bson/oid.h"
#include "mongo/db/jsobj.h"
@@ -327,6 +328,12 @@ Status validateBSONIterative(Buffer* buffer) {
while (state != ValidationState::Done) {
switch (state) {
case ValidationState::BeginObj:
+ if (frames.size() > BSONDepth::getMaxAllowableDepth()) {
+ return {ErrorCodes::Overflow,
+ str::stream() << "BSONObj exceeded maximum nested object depth: "
+ << BSONDepth::getMaxAllowableDepth()};
+ }
+
frames.push_back(ValidationObjectFrame());
curr = &frames.back();
curr->setStartPosition(buffer->position());
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 8aba94b60fe..2d1399edcf9 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -95,14 +95,14 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c
const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level) {
+ bool topLevel) {
// TODO: these should move to getGtLtOp, or its replacement
if (mongoutils::str::equals("$eq", e.fieldName()))
return _parseComparison(name, new EqualityMatchExpression(), e, collator);
if (mongoutils::str::equals("$not", e.fieldName())) {
- return _parseNot(name, e, collator, level);
+ return _parseNot(name, e, collator, topLevel);
}
int x = e.getGtLtOp(-1);
@@ -247,10 +247,10 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c
}
case BSONObj::opELEM_MATCH:
- return _parseElemMatch(name, e, collator, level);
+ return _parseElemMatch(name, e, collator, topLevel);
case BSONObj::opALL:
- return _parseAll(name, e, collator, level);
+ return _parseAll(name, e, collator, topLevel);
case BSONObj::opWITHIN:
case BSONObj::opGEO_INTERSECTS:
@@ -285,19 +285,10 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c
StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj,
const CollatorInterface* collator,
- int level) {
- if (level > kMaximumTreeDepth) {
- mongoutils::str::stream ss;
- ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at "
- << obj.toString();
- return {Status(ErrorCodes::BadValue, ss)};
- }
-
+ bool topLevel) {
std::unique_ptr<AndMatchExpression> root = stdx::make_unique<AndMatchExpression>();
- bool topLevel = (level == 0);
- level++;
-
+ const bool childIsTopLevel = false;
BSONObjIterator i(obj);
while (i.more()) {
BSONElement e = i.next();
@@ -309,7 +300,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj,
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$or must be an array")};
std::unique_ptr<OrMatchExpression> temp = stdx::make_unique<OrMatchExpression>();
- Status s = _parseTreeList(e.Obj(), temp.get(), collator, level);
+ Status s = _parseTreeList(e.Obj(), temp.get(), collator, childIsTopLevel);
if (!s.isOK())
return s;
root->add(temp.release());
@@ -317,7 +308,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj,
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$and must be an array")};
std::unique_ptr<AndMatchExpression> temp = stdx::make_unique<AndMatchExpression>();
- Status s = _parseTreeList(e.Obj(), temp.get(), collator, level);
+ Status s = _parseTreeList(e.Obj(), temp.get(), collator, childIsTopLevel);
if (!s.isOK())
return s;
root->add(temp.release());
@@ -325,7 +316,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj,
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$nor must be an array")};
std::unique_ptr<NorMatchExpression> temp = stdx::make_unique<NorMatchExpression>();
- Status s = _parseTreeList(e.Obj(), temp.get(), collator, level);
+ Status s = _parseTreeList(e.Obj(), temp.get(), collator, childIsTopLevel);
if (!s.isOK())
return s;
root->add(temp.release());
@@ -370,7 +361,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj,
}
if (_isExpressionDocument(e, false)) {
- Status s = _parseSub(e.fieldName(), e.Obj(), root.get(), collator, level);
+ Status s = _parseSub(e.fieldName(), e.Obj(), root.get(), collator, childIsTopLevel);
if (!s.isOK())
return s;
continue;
@@ -407,22 +398,13 @@ Status MatchExpressionParser::_parseSub(const char* name,
const BSONObj& sub,
AndMatchExpression* root,
const CollatorInterface* collator,
- int level) {
+ bool topLevel) {
// The one exception to {field : {fully contained argument} } is, of course, geo. Example:
// sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } }
// We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over
// its subfields and parse them one at a time (there is no $maxDistance without $near), so
// we hand the entire object over to the geo parsing routines.
- if (level > kMaximumTreeDepth) {
- mongoutils::str::stream ss;
- ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at "
- << sub.toString();
- return Status(ErrorCodes::BadValue, ss);
- }
-
- level++;
-
// Special case parsing for geoNear. This is necessary in order to support query formats like
// {$near: <coords>, $maxDistance: <distance>}. No other query operators allow $-prefixed
// modifiers as sibling BSON elements.
@@ -452,7 +434,9 @@ Status MatchExpressionParser::_parseSub(const char* name,
while (j.more()) {
BSONElement deep = j.next();
- StatusWithMatchExpression s = _parseSubField(sub, root, name, deep, collator, level);
+ const bool childIsTopLevel = false;
+ StatusWithMatchExpression s =
+ _parseSubField(sub, root, name, deep, collator, childIsTopLevel);
if (!s.isOK())
return s.getStatus();
@@ -682,7 +666,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseType(const char* name,
StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level) {
+ bool topLevel) {
if (e.type() != Object)
return {Status(ErrorCodes::BadValue, "$elemMatch needs an Object")};
@@ -712,7 +696,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* nam
// value case
AndMatchExpression theAnd;
- Status s = _parseSub("", obj, &theAnd, collator, level);
+ Status s = _parseSub("", obj, &theAnd, collator, topLevel);
if (!s.isOK())
return s;
@@ -736,7 +720,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* nam
// object case
- StatusWithMatchExpression subRaw = _parse(obj, collator, level);
+ StatusWithMatchExpression subRaw = _parse(obj, collator, topLevel);
if (!subRaw.isOK())
return subRaw;
std::unique_ptr<MatchExpression> sub = std::move(subRaw.getValue());
@@ -759,7 +743,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* nam
StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level) {
+ bool topLevel) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$all needs an array")};
@@ -787,8 +771,9 @@ StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name,
return {Status(ErrorCodes::BadValue, "$all/$elemMatch has to be consistent")};
}
- StatusWithMatchExpression inner =
- _parseElemMatch(name, hopefullyElemMatchObj.firstElement(), collator, level);
+ const bool childIsTopLevel = false;
+ StatusWithMatchExpression inner = _parseElemMatch(
+ name, hopefullyElemMatchObj.firstElement(), collator, childIsTopLevel);
if (!inner.isOK())
return inner;
myAnd->add(inner.getValue().release());
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index 9df3a7aa874..8141726251b 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -52,8 +52,8 @@ public:
static StatusWithMatchExpression parse(const BSONObj& obj,
const ExtensionsCallback& extensionsCallback,
const CollatorInterface* collator) {
- // The 0 initializes the match expression tree depth.
- return MatchExpressionParser(&extensionsCallback)._parse(obj, collator, 0);
+ const bool topLevelCall = true;
+ return MatchExpressionParser(&extensionsCallback)._parse(obj, collator, topLevelCall);
}
private:
@@ -86,13 +86,12 @@ private:
* 'collator' is the collator that constructed collation-aware MatchExpressions will use. It
* must outlive the returned MatchExpression and any clones made of it.
*
- * 'level' tracks the current depth of the tree across recursive calls to this
- * function. Used in order to apply special logic at the top-level and to return an
- * error if the tree exceeds the maximum allowed depth.
+ * 'topLevel' indicates whether or not the we are at the top level of the tree across recursive
+ * class to this function. This is used to apply special logic at the top level.
*/
StatusWithMatchExpression _parse(const BSONObj& obj,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
/**
* parses a field in a sub expression
@@ -103,7 +102,7 @@ private:
const BSONObj& obj,
AndMatchExpression* root,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
/**
* parses a single field in a sub expression
@@ -115,7 +114,7 @@ private:
const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
StatusWithMatchExpression _parseComparison(const char* name,
ComparisonMatchExpression* cmp,
@@ -140,24 +139,24 @@ private:
StatusWithMatchExpression _parseElemMatch(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
StatusWithMatchExpression _parseAll(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
// tree
Status _parseTreeList(const BSONObj& arr,
ListOfMatchExpression* out,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
StatusWithMatchExpression _parseNot(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level);
+ bool topLevel);
/**
* Parses 'e' into a BitTestMatchExpression.
diff --git a/src/mongo/db/matcher/expression_parser_tree.cpp b/src/mongo/db/matcher/expression_parser_tree.cpp
index 1d1761f96e6..91abc73cbfa 100644
--- a/src/mongo/db/matcher/expression_parser_tree.cpp
+++ b/src/mongo/db/matcher/expression_parser_tree.cpp
@@ -39,14 +39,10 @@
#include "mongo/util/mongoutils/str.h"
namespace mongo {
-
-// static
-const int MatchExpressionParser::kMaximumTreeDepth = 100;
-
Status MatchExpressionParser::_parseTreeList(const BSONObj& arr,
ListOfMatchExpression* out,
const CollatorInterface* collator,
- int level) {
+ bool topLevel) {
if (arr.isEmpty())
return Status(ErrorCodes::BadValue, "$and/$or/$nor must be a nonempty array");
@@ -57,7 +53,7 @@ Status MatchExpressionParser::_parseTreeList(const BSONObj& arr,
if (e.type() != Object)
return Status(ErrorCodes::BadValue, "$or/$and/$nor entries need to be full objects");
- StatusWithMatchExpression sub = _parse(e.Obj(), collator, level);
+ StatusWithMatchExpression sub = _parse(e.Obj(), collator, topLevel);
if (!sub.isOK())
return sub.getStatus();
@@ -69,7 +65,7 @@ Status MatchExpressionParser::_parseTreeList(const BSONObj& arr,
StatusWithMatchExpression MatchExpressionParser::_parseNot(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
- int level) {
+ bool topLevel) {
if (e.type() == RegEx) {
StatusWithMatchExpression s = _parseRegexElement(name, e);
if (!s.isOK())
@@ -89,7 +85,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseNot(const char* name,
return StatusWithMatchExpression(ErrorCodes::BadValue, "$not cannot be empty");
std::unique_ptr<AndMatchExpression> theAnd = stdx::make_unique<AndMatchExpression>();
- Status s = _parseSub(name, notObject, theAnd.get(), collator, level);
+ Status s = _parseSub(name, notObject, theAnd.get(), collator, topLevel);
if (!s.isOK())
return StatusWithMatchExpression(s);
diff --git a/src/mongo/db/matcher/expression_parser_tree_test.cpp b/src/mongo/db/matcher/expression_parser_tree_test.cpp
index e3c17b7bc91..1fbed8b69a6 100644
--- a/src/mongo/db/matcher/expression_parser_tree_test.cpp
+++ b/src/mongo/db/matcher/expression_parser_tree_test.cpp
@@ -107,87 +107,6 @@ TEST(MatchExpressionParserTreeTest, NOT1) {
ASSERT(!result.getValue()->matchesBSON(BSON("x" << 8)));
}
-// Test a deep match tree that is not deep enough to hit the maximum depth limit.
-TEST(MatchExpressionParserTreeTest, MaximumTreeDepthNotExceed) {
- static const int depth = 60;
-
- std::stringstream ss;
- for (int i = 0; i < depth / 2; i++) {
- ss << "{$and: [{a: 3}, {$or: [{b: 2},";
- }
- ss << "{b: 4}";
- for (int i = 0; i < depth / 2; i++) {
- ss << "]}]}";
- }
-
- BSONObj query = fromjson(ss.str());
- const CollatorInterface* collator = nullptr;
- StatusWithMatchExpression result =
- MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator);
- ASSERT(result.isOK());
-}
-
-// Test a tree that exceeds the maximum depth limit.
-TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceed) {
- static const int depth = 105;
-
- std::stringstream ss;
- for (int i = 0; i < depth / 2; i++) {
- ss << "{$and: [{a: 3}, {$or: [{b: 2},";
- }
- ss << "{b: 4}";
- for (int i = 0; i < depth / 2; i++) {
- ss << "]}]}";
- }
-
- BSONObj query = fromjson(ss.str());
- const CollatorInterface* collator = nullptr;
- StatusWithMatchExpression result =
- MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator);
- ASSERT_FALSE(result.isOK());
-}
-
-// We should also exceed the depth limit through deeply nested $not.
-TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedNots) {
- static const int depth = 105;
-
- std::stringstream ss;
- ss << "{a: ";
- for (int i = 0; i < depth; i++) {
- ss << "{$not: ";
- }
- ss << "{$eq: 5}";
- for (int i = 0; i < depth + 1; i++) {
- ss << "}";
- }
-
- BSONObj query = fromjson(ss.str());
- const CollatorInterface* collator = nullptr;
- StatusWithMatchExpression result =
- MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator);
- ASSERT_FALSE(result.isOK());
-}
-
-// Depth limit with nested $elemMatch object.
-TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedElemMatch) {
- static const int depth = 105;
-
- std::stringstream ss;
- for (int i = 0; i < depth; i++) {
- ss << "{a: {$elemMatch: ";
- }
- ss << "{b: 5}";
- for (int i = 0; i < depth; i++) {
- ss << "}}";
- }
-
- BSONObj query = fromjson(ss.str());
- const CollatorInterface* collator = nullptr;
- StatusWithMatchExpression result =
- MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator);
- ASSERT_FALSE(result.isOK());
-}
-
TEST(MatchExpressionParserLeafTest, NotRegex1) {
BSONObjBuilder b;
b.appendRegex("$not", "abc", "i");
diff --git a/src/mongo/rpc/object_check.cpp b/src/mongo/rpc/object_check.cpp
index 98ddda58697..37a8a8c77a6 100644
--- a/src/mongo/rpc/object_check.cpp
+++ b/src/mongo/rpc/object_check.cpp
@@ -25,14 +25,38 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
-
#include "mongo/platform/basic.h"
#include "mongo/rpc/object_check.h"
#include "mongo/base/status.h"
+#include "mongo/bson/bson_depth.h"
+#include "mongo/db/server_parameters.h"
+#include "mongo/util/stringutils.h"
namespace mongo {
+namespace {
+class MaxBSONDepthParameter
+ : public ExportedServerParameter<std::int32_t, ServerParameterType::kStartupOnly> {
+public:
+ MaxBSONDepthParameter()
+ : ExportedServerParameter<std::int32_t, ServerParameterType::kStartupOnly>(
+ ServerParameterSet::getGlobal(), "maxBSONDepth", &BSONDepth::maxAllowableDepth) {}
+
+ virtual Status validate(const std::int32_t& potentialNewValue) {
+ if (potentialNewValue < BSONDepth::kBSONDepthParameterFloor ||
+ potentialNewValue > BSONDepth::kBSONDepthParameterCeiling) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "maxBSONDepth must be between "
+ << BSONDepth::kBSONDepthParameterFloor
+ << " and "
+ << BSONDepth::kBSONDepthParameterCeiling
+ << ", inclusive");
+ }
+ return Status::OK();
+ }
+} maxBSONDepthParameter;
+} // namespace
Status Validator<BSONObj>::validateStore(const BSONObj& toStore) {
return Status::OK();