# UNDF: UNDF-2026-000000173 diff --git a/src/mongo/db/query/index_tag.h b/src/mongo/db/query/index_tag.h index 7465ab84..db15bf62 100644 --- a/src/mongo/db/query/index_tag.h +++ b/src/mongo/db/query/index_tag.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -103,8 +104,11 @@ class RelevantTag final : public MatchExpression::TagData { public: RelevantTag() : elemMatchExpr(nullptr), pathPrefix("") {} - std::vector first; - std::vector notFirst; + // CWE-407 fix: changed from std::vector to std::unordered_set so that + // membership tests in isIndexAssigned() and stripInvalidAssignments*() are O(1) instead of + // O(n), eliminating the O(P*I) quadratic scan in the index selection strip passes. + std::unordered_set first; + std::unordered_set notFirst; // We don't know the full path from a node unless we keep notes as we traverse from the // root. We do this once and store it. @@ -133,12 +137,12 @@ public: void debugString(StringBuilder* builder) const override { *builder << " || First: "; - for (size_t i = 0; i < first.size(); ++i) { - *builder << first[i] << " "; + for (size_t idx : first) { + *builder << idx << " "; } *builder << "notFirst: "; - for (size_t i = 0; i < notFirst.size(); ++i) { - *builder << notFirst[i] << " "; + for (size_t idx : notFirst) { + *builder << idx << " "; } *builder << "full path: " << path << "\n"; } diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp index 90392cbe..39904213 100644 --- a/src/mongo/db/query/planner_ixselect.cpp +++ b/src/mongo/db/query/planner_ixselect.cpp @@ -816,9 +816,9 @@ void QueryPlannerIXSelect::rateIndices(MatchExpression* node, queryContext, nodeIsNotChild)) { if (keyPatternIndex == 0) { - rt->first.push_back(i); + rt->first.insert(i); } else { - rt->notFirst.push_back(i); + rt->notFirst.insert(i); } } ++keyPatternIndex; @@ -937,17 +937,14 @@ void QueryPlannerIXSelect::stripUnneededAssignments(MatchExpression* node, // Look through all of the indices for which this predicate can be answered with // the leading field of the index. - for (std::vector::const_iterator i = rt->first.begin(); i != rt->first.end(); - ++i) { - size_t index = *i; - + for (size_t index : rt->first) { if (indices[index].unique && 1 == indices[index].keyPattern.nFields()) { // Found an EQ predicate which can use a single-field unique index. // Clear assignments from the entire tree, and add back a single assignment // for 'child' to the unique index. clearAssignments(node); RelevantTag* newRt = indexTagCast(child->getTag()); - newRt->first.push_back(index); + newRt->first.insert(index); // Tag state has been reset in the entire subtree at 'root'; nothing // else for us to do. @@ -975,16 +972,9 @@ static void removeIndexRelevantTag(MatchExpression* node, size_t idx) { return; } - vector::iterator firstIt = std::find(tag->first.begin(), tag->first.end(), idx); - if (firstIt != tag->first.end()) { - tag->first.erase(firstIt); - } - - vector::iterator notFirstIt = - std::find(tag->notFirst.begin(), tag->notFirst.end(), idx); - if (notFirstIt != tag->notFirst.end()) { - tag->notFirst.erase(notFirstIt); - } + // CWE-407 fix: unordered_set::erase(value) is O(1); replaces O(n) std::find + erase. + tag->first.erase(idx); + tag->notFirst.erase(idx); } namespace { @@ -1081,10 +1071,8 @@ bool isIndexAssigned(RelevantTag* tag, size_t idx) { return false; } - bool inFirst = tag->first.end() != std::find(tag->first.begin(), tag->first.end(), idx); - bool inNotFirst = - tag->notFirst.end() != std::find(tag->notFirst.begin(), tag->notFirst.end(), idx); - return inFirst || inNotFirst; + // CWE-407 fix: O(1) set lookup replaces O(n) std::find on vector. + return tag->first.count(idx) || tag->notFirst.count(idx); } // Returns true for $and and $elemMatch as they consist of a set of predicates that are suitable for @@ -1307,10 +1295,9 @@ static void stripInvalidAssignmentsToTextIndex(MatchExpression* node, continue; } - bool inFirst = tag->first.end() != std::find(tag->first.begin(), tag->first.end(), idx); - - bool inNotFirst = - tag->notFirst.end() != std::find(tag->notFirst.begin(), tag->notFirst.end(), idx); + // CWE-407 fix: O(1) set lookup replaces O(n) std::find. + bool inFirst = tag->first.count(idx) != 0; + bool inNotFirst = tag->notFirst.count(idx) != 0; if (inFirst || inNotFirst) { // Great! 'child' was assigned to our index. @@ -1421,10 +1408,9 @@ static void stripInvalidAssignmentsTo2dsphereIndex(MatchExpression* node, size_t continue; } - bool inFirst = tag->first.end() != std::find(tag->first.begin(), tag->first.end(), idx); - - bool inNotFirst = - tag->notFirst.end() != std::find(tag->notFirst.begin(), tag->notFirst.end(), idx); + // CWE-407 fix: O(1) set lookup replaces O(n) std::find. + bool inFirst = tag->first.count(idx) != 0; + bool inNotFirst = tag->notFirst.count(idx) != 0; // If there is an index assignment... if (inFirst || inNotFirst) {