summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2014-04-25 14:16:46 -0400
committerDan Pasette <dan@mongodb.com>2014-05-15 19:16:10 -0400
commit5f50fe901bdd34c5c539c612e22b30f339e57e81 (patch)
treea19ba7d9e44055ccdc3acedf86148c1d1593d92e
parentadc534fcdd0425f689954f44a940f6416e46673a (diff)
downloadmongo-5f50fe901bdd34c5c539c612e22b30f339e57e81.tar.gz
SERVER-13731 make depth limit in parsing work for deep trees other than AND and OR
(cherry picked from commit 78c850d9c512596a3a8f7e937840d67f52a14baf)
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp38
-rw-r--r--src/mongo/db/matcher/expression_parser.h16
-rw-r--r--src/mongo/db/matcher/expression_parser_tree.cpp5
-rw-r--r--src/mongo/db/matcher/expression_parser_tree_test.cpp37
4 files changed, 77 insertions, 19 deletions
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 2dd26cc4b4f..e8b1ad250ce 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -87,7 +87,8 @@ namespace mongo {
StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context,
const AndMatchExpression* andSoFar,
const char* name,
- const BSONElement& e ) {
+ const BSONElement& e,
+ int level ) {
// TODO: these should move to getGtLtOp, or its replacement
@@ -95,7 +96,7 @@ namespace mongo {
return _parseComparison( name, new EqualityMatchExpression(), e );
if ( mongoutils::str::equals( "$not", e.fieldName() ) ) {
- return _parseNot( name, e );
+ return _parseNot( name, e, level );
}
int x = e.getGtLtOp(-1);
@@ -258,10 +259,10 @@ namespace mongo {
}
case BSONObj::opELEM_MATCH:
- return _parseElemMatch( name, e );
+ return _parseElemMatch( name, e, level );
case BSONObj::opALL:
- return _parseAll( name, e );
+ return _parseAll( name, e, level );
case BSONObj::opWITHIN:
case BSONObj::opGEO_INTERSECTS:
@@ -377,7 +378,7 @@ namespace mongo {
}
if ( _isExpressionDocument( e, false ) ) {
- Status s = _parseSub( e.fieldName(), e.Obj(), root.get() );
+ Status s = _parseSub( e.fieldName(), e.Obj(), root.get(), level );
if ( !s.isOK() )
return StatusWithMatchExpression( s );
continue;
@@ -410,13 +411,23 @@ namespace mongo {
Status MatchExpressionParser::_parseSub( const char* name,
const BSONObj& sub,
- AndMatchExpression* root ) {
+ AndMatchExpression* root,
+ int level ) {
// 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++;
+
BSONObjIterator geoIt(sub);
if (geoIt.more()) {
BSONElement firstElt = geoIt.next();
@@ -447,7 +458,7 @@ namespace mongo {
while ( j.more() ) {
BSONElement deep = j.next();
- StatusWithMatchExpression s = _parseSubField( sub, root, name, deep );
+ StatusWithMatchExpression s = _parseSubField( sub, root, name, deep, level );
if ( !s.isOK() )
return s.getStatus();
@@ -636,7 +647,8 @@ namespace mongo {
}
StatusWithMatchExpression MatchExpressionParser::_parseElemMatch( const char* name,
- const BSONElement& e ) {
+ const BSONElement& e,
+ int level ) {
if ( e.type() != Object )
return StatusWithMatchExpression( ErrorCodes::BadValue, "$elemMatch needs an Object" );
@@ -666,7 +678,7 @@ namespace mongo {
// value case
AndMatchExpression theAnd;
- Status s = _parseSub( "", obj, &theAnd );
+ Status s = _parseSub( "", obj, &theAnd, level );
if ( !s.isOK() )
return StatusWithMatchExpression( s );
@@ -689,7 +701,7 @@ namespace mongo {
// object case
- StatusWithMatchExpression sub = _parse( obj, false );
+ StatusWithMatchExpression sub = _parse( obj, level );
if ( !sub.isOK() )
return sub;
@@ -709,7 +721,8 @@ namespace mongo {
}
StatusWithMatchExpression MatchExpressionParser::_parseAll( const char* name,
- const BSONElement& e ) {
+ const BSONElement& e,
+ int level ) {
if ( e.type() != Array )
return StatusWithMatchExpression( ErrorCodes::BadValue, "$all needs an array" );
@@ -742,7 +755,8 @@ namespace mongo {
"$all/$elemMatch has to be consistent" );
}
- StatusWithMatchExpression inner = _parseElemMatch( "", hopefullyElemMatchObj.firstElement() );
+ StatusWithMatchExpression inner =
+ _parseElemMatch( "", hopefullyElemMatchObj.firstElement(), level );
if ( !inner.isOK() )
return inner;
temp->add( static_cast<ArrayMatchingMatchExpression*>( inner.getValue() ) );
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index 6b714429920..b4c45e6cb81 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -92,7 +92,8 @@ namespace mongo {
*/
static Status _parseSub( const char* name,
const BSONObj& obj,
- AndMatchExpression* root );
+ AndMatchExpression* root,
+ int level );
/**
* parses a single field in a sub expression
@@ -102,7 +103,8 @@ namespace mongo {
static StatusWithMatchExpression _parseSubField( const BSONObj& context,
const AndMatchExpression* andSoFar,
const char* name,
- const BSONElement& e );
+ const BSONElement& e,
+ int level );
static StatusWithMatchExpression _parseComparison( const char* name,
ComparisonMatchExpression* cmp,
@@ -124,16 +126,20 @@ namespace mongo {
// arrays
static StatusWithMatchExpression _parseElemMatch( const char* name,
- const BSONElement& e );
+ const BSONElement& e,
+ int level );
static StatusWithMatchExpression _parseAll( const char* name,
- const BSONElement& e );
+ const BSONElement& e,
+ int level );
// tree
static Status _parseTreeList( const BSONObj& arr, ListOfMatchExpression* out, int level );
- static StatusWithMatchExpression _parseNot( const char* name, const BSONElement& e );
+ static StatusWithMatchExpression _parseNot( const char* name,
+ const BSONElement& e,
+ int level );
// The maximum allowed depth of a query tree. Just to guard against stack overflow.
static const int kMaximumTreeDepth;
diff --git a/src/mongo/db/matcher/expression_parser_tree.cpp b/src/mongo/db/matcher/expression_parser_tree.cpp
index aa36aff60b2..04a25dac779 100644
--- a/src/mongo/db/matcher/expression_parser_tree.cpp
+++ b/src/mongo/db/matcher/expression_parser_tree.cpp
@@ -70,7 +70,8 @@ namespace mongo {
}
StatusWithMatchExpression MatchExpressionParser::_parseNot( const char* name,
- const BSONElement& e ) {
+ const BSONElement& e,
+ int level ) {
if ( e.type() == RegEx ) {
StatusWithMatchExpression s = _parseRegexElement( name, e );
if ( !s.isOK() )
@@ -90,7 +91,7 @@ namespace mongo {
return StatusWithMatchExpression( ErrorCodes::BadValue, "$not cannot be empty" );
std::auto_ptr<AndMatchExpression> theAnd( new AndMatchExpression() );
- Status s = _parseSub( name, notObject, theAnd.get() );
+ Status s = _parseSub( name, notObject, theAnd.get(), level );
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 eec96a91049..aba286fb4c3 100644
--- a/src/mongo/db/matcher/expression_parser_tree_test.cpp
+++ b/src/mongo/db/matcher/expression_parser_tree_test.cpp
@@ -136,6 +136,43 @@ namespace mongo {
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() );
+ StatusWithMatchExpression result = MatchExpressionParser::parse( query );
+ 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() );
+ StatusWithMatchExpression result = MatchExpressionParser::parse( query );
+ ASSERT_FALSE( result.isOK() );
+ }
+
TEST( MatchExpressionParserLeafTest, NotRegex1 ) {
BSONObjBuilder b;
b.appendRegex( "$not", "abc", "i" );