diff options
Diffstat (limited to 'src/mongo/bson/bson_validate.cpp')
| -rw-r--r-- | src/mongo/bson/bson_validate.cpp | 234 |
1 files changed, 24 insertions, 210 deletions
diff --git a/src/mongo/bson/bson_validate.cpp b/src/mongo/bson/bson_validate.cpp index da4b54ef194..08659efecd1 100644 --- a/src/mongo/bson/bson_validate.cpp +++ b/src/mongo/bson/bson_validate.cpp @@ -35,7 +35,6 @@ #include "mongo/bson/bson_depth.h" #include "mongo/bson/bson_validate.h" #include "mongo/bson/bsonelement.h" -#include "mongo/bson/util/bsoncolumn_util.h" #include "mongo/logv2/log.h" namespace mongo { @@ -80,25 +79,21 @@ static constexpr ValidationStyle kTypeInfoTable alignas(32)[32] = { MONGO_STATIC_ASSERT(sizeof(kTypeInfoTable) == 32); constexpr ErrorCodes::Error InvalidBSON = ErrorCodes::InvalidBSON; -constexpr ErrorCodes::Error NonConformantBSON = ErrorCodes::NonConformantBSON; - -template <bool precise> -Status _doValidateColumn(const char* originalBuffer, - uint64_t maxLength, - ValidationVersion validationVersion); template <bool precise> class ValidateBuffer { public: - ValidateBuffer(const char* data, uint64_t maxLength, ValidationVersion validationVersion) - : _data(data), _maxLength(maxLength), _validationVersion(validationVersion) { + ValidateBuffer(const char* data, uint64_t maxLength) : _data(data), _maxLength(maxLength) { if constexpr (precise) _frames.resize(BSONDepth::getMaxAllowableDepth() + 1); } Status validate() noexcept { try { - setupValidation(); + _currFrame = _frames.begin(); + _currElem = nullptr; + auto maxFrames = BSONDepth::getMaxAllowableDepth() + 1; // A flat BSON has one frame. + uassert(InvalidBSON, "Cannot enforce max nesting depth", _frames.size() <= maxFrames); uassert(InvalidBSON, "BSON data has to be at least 5 bytes", _maxLength >= 5); // Read the length as signed integer, to ensure we limit it to < 2GB. @@ -116,52 +111,9 @@ public: return Status::OK(); } - /* Assumes the root level is a single literal element (which may contain nested objects). - * Only validates up to the termination of that first literal, more data is permitted to - * remain in the buffer after that and is not validated. Throws exception on invalid data. - * Confirm field names for literals in BSONColumn have empty field names. - */ - int validateAndMeasureElem() { - setupValidation(); - uassert(InvalidBSON, - "BSON literal is not followed by fieldname", - _maxLength > 1); // must at least have a 0-terminator after control - // Confirm fieldName is just a null terminator - uassert(NonConformantBSON, - "BSON literal content does not have an empty fieldname", - _maxLength > 1 && _data[1] == 0); - - // Handle one element without using iterative loop, and without expecting - // multiple instances or an EOO. Only resume with the iterative loop if - // we have nested objects - _currElem = _data; - const char* ptr = _validateElem<false>(Cursor{_data + 2, _data + _maxLength}, *_data); - - if (_firstFrameUpdated) { - // We know that type was kObject/kArray/kCodeWScope - // Size is fieldname, type, and a stored int - int64_t size = - static_cast<int64_t>(ConstDataView(_data + 2).read<LittleEndian<int32_t>>()) + 2; - uassert(InvalidBSON, - "BSON literal content exceeds buffer size", - (size_t)size <= _maxLength); - _validateIterative(Cursor{ptr, _data + size}); - return size; - } else { - return ptr - _data; - } - } - private: struct Empty {}; - void inline setupValidation() { - _currFrame = _frames.begin(); - _currElem = nullptr; - auto maxFrames = BSONDepth::getMaxAllowableDepth() + 1; // A flat BSON has one frame. - uassert(InvalidBSON, "Cannot enforce max nesting depth", _frames.size() <= maxFrames); - } - /** * Extra information for each nesting level in the precise validation mode. */ @@ -177,8 +129,6 @@ private: typename std::conditional<precise, std::vector<Frame>, std::array<Frame, 32>>::type; struct Cursor { - /* Also requires remaining buf after the skip (both BSONColumn and BSONObj guarantee this - by having at minimum a trailing EOO) */ void skip(size_t len) { uassert(InvalidBSON, "BSON size is larger than buffer size", (ptr += len) < end); } @@ -213,10 +163,7 @@ private: uassert(ErrorCodes::Overflow, "BSONObj exceeds maximum nested object depth", ++_currFrame != _frames.end()); - return _updateFrame(cursor); - } - const char* _updateFrame(Cursor cursor) { auto obj = cursor.ptr; auto len = cursor.template read<int32_t>(); uassert(ErrorCodes::InvalidBSON, "Nested BSON object has to be at least 5 bytes", len >= 5); @@ -236,22 +183,12 @@ private: return true; } - const char* _validateSpecial(Cursor cursor, uint8_t type) { + static const char* _validateSpecial(Cursor cursor, uint8_t type) { switch (type) { - case BSONType::BinData: { - auto count = cursor.template read<uint32_t>(); - auto subtype = cursor.template read<uint8_t>(); - const char* columnStart = cursor.ptr; - cursor.skip(count); - if (subtype == BinDataType::Column && _validationVersion >= V2_Column) { - /* do not pass down cursor; we want to reset the nesting depth */ - uassert( - NonConformantBSON, - "Invalid BSON column", - _doValidateColumn<precise>(columnStart, count, _validationVersion).isOK()); - } + case BSONType::BinData: + cursor.skip(cursor.template read<uint32_t>()); // Like String, but... + cursor.skip(1); // ...add extra skip for the subtype byte to avoid overflow. break; - } case BSONType::Bool: if (auto value = cursor.template read<uint8_t>()) // If not 0, must be 1. uassert(InvalidBSON, "BSON bool is neither false nor true", value == 1); @@ -275,15 +212,10 @@ private: return cursor.ptr; } - template <bool nestedFrame> const char* _pushCodeWithScope(Cursor cursor) { - // Push a dummy frame to check the CodeWScope size. - if constexpr (nestedFrame) - cursor.ptr = _pushFrame(cursor); - else - cursor.ptr = _updateFrame(cursor); - cursor.skipString(); // Now skip the BSON UTF8 string containing the code. - _currElem = cursor.ptr - 1; // Use the terminating NUL as a dummy scope element. + cursor.ptr = _pushFrame(cursor); // Push a dummy frame to check the CodeWScope size. + cursor.skipString(); // Now skip the BSON UTF8 string containing the code. + _currElem = cursor.ptr - 1; // Use the terminating NUL as adummy scope element. return _pushFrame(cursor); } @@ -297,30 +229,21 @@ private: } } - template <bool nestedFrame> const char* _validateElem(Cursor cursor, uint8_t type) { if (MONGO_unlikely(type > JSTypeMax)) return _validateSpecial(cursor, type); auto style = kTypeInfoTable[type]; - if (MONGO_likely(style <= kSkip16)) { + if (MONGO_likely(style <= kSkip16)) cursor.skip(style * 4); - } else if (MONGO_likely(style == kString)) { + else if (MONGO_likely(style == kString)) cursor.skipString(); - } else if (MONGO_likely(style == kObjectOrArray)) { - if constexpr (nestedFrame) { - cursor.ptr = _pushFrame(cursor); - } else { - cursor.ptr = _updateFrame(cursor); - _firstFrameUpdated = true; - } - } else if (MONGO_unlikely(precise && type == CodeWScope)) { - cursor.ptr = _pushCodeWithScope<nestedFrame>(cursor); - if constexpr (!nestedFrame) - _firstFrameUpdated = true; - } else { + else if (MONGO_likely(style == kObjectOrArray)) + cursor.ptr = _pushFrame(cursor); + else if (MONGO_unlikely(precise && type == CodeWScope)) + cursor.ptr = _pushCodeWithScope(cursor); + else cursor.ptr = _validateSpecial(cursor, type); - } return cursor.ptr; } @@ -334,14 +257,14 @@ private: uint8_t type = *cursor.ptr; _currElem = cursor.ptr; cursor.ptr += len + 1; - cursor.ptr = _validateElem<true>(cursor, type); + cursor.ptr = _validateElem(cursor, type); if constexpr (precise) { // See if the _id field was just validated. If so, set the global scope element. if (_currFrame == _frames.begin() && StringData(_currElem + 1) == "_id"_sd) _currFrame->elem = BSONElement(_currElem); // This is fully validated now. } - dassert(cursor.ptr < cursor.end); + dassert(cursor.ptr <= cursor.end); } // Got the EOO byte: skip it and compare its location with the expected frame end. @@ -376,125 +299,16 @@ private: const char* _currElem = nullptr; // Element to validate: only the name is known to be good. typename Frames::iterator _currFrame; // Frame currently being validated. Frames _frames; // Has end pointers to check and the containing element for precise mode. - bool _firstFrameUpdated = false; // Has the first frame received nested while measuring an elem - ValidationVersion _validationVersion; -}; - -template <bool precise> -class ColumnValidator { -public: - static Status doValidateBSONColumn(const char* originalBuffer, - int maxLength, - ValidationVersion validationVersion) noexcept { - // run control pointer through to end of buffer - // run over literal data as directed by lengths from control - // check formatting of Simple8B blocks - // scan reference objects of interleaved mode starts - // confirm EOO terminations of interleaved modes - // content of interleaved objects does not need to be checked differently from - // standard Simple8B block and literal decodings - // confirm we end at end of buffer - const char* ptr = originalBuffer; - const char* end = originalBuffer + maxLength; - bool interleavedMode = false; - - try { - // Check this beforehand to ensure we cannot overflow the buffer with any strlen - uassert(NonConformantBSON, - "BSON column is missing EOO termination", - ptr < end && *(end - 1) == EOO); - - while (ptr < end) { - uint8_t control = *ptr; - if (control == EOO) { - ptr++; - if (interleavedMode) { - interleavedMode = false; - } else { - // should be the last control of the sequence - uassert(NonConformantBSON, - "BSONColumn EOO does not fully consume buffer", - ptr == end); - return Status::OK(); - } - } else if (bsoncolumn::isUncompressedLiteralControlByte(control)) { - ptr += ValidateBuffer<precise>(ptr, end - ptr, validationVersion) - .validateAndMeasureElem(); - } else if (bsoncolumn::isInterleavedStartControlByte(control)) { - // interleaved objects begin with a reference object, and then a series - // of diff blocks for followup objects, ending with an EOO. Nesting interleaved - // mode is not allowed. - uassert(NonConformantBSON, "Nested interleaved mode", !interleavedMode); - ptr++; - uassert(NonConformantBSON, - "Invalid reference object for interleaved mode", - validateBSON(ptr, end - ptr).isOK()); - // we now know due to validateBSON that it is safe to interpret *ptr - BSONObj reference(ptr); - ptr += reference.objsize(); - interleavedMode = true; - } else { - // Simple8b block sequence, just check for memory overflow of block count - uint8_t numBlocks = bsoncolumn::numSimple8bBlocksForControlByte(control); - int size = sizeof(uint64_t) * numBlocks; - uassert(NonConformantBSON, - "BSONColumn blocks exceed buffer size", - ptr + size + 1 <= end); - ptr += 1 + size; - } - } - } catch (const ExceptionForCat<ErrorCategory::ValidationError>& e) { - return Status(e.code(), str::stream() << e.what()); - } - - // We should not get here for a valid object, the final EOO should have returned OK - return Status(NonConformantBSON, "Missing terminating EOO"); - } }; - -template <bool precise> -Status _doValidateColumn(const char* originalBuffer, - uint64_t maxLength, - ValidationVersion validationVersion) { - if constexpr (precise) { - // First try validating using the fast but less precise version. That version will return - // a not-OK status for objects with CodeWScope or nesting exceeding 32 levels. These cases - // and actual failures will rerun the precise version that gives a detailed error context. - if (MONGO_likely(ColumnValidator<false>::doValidateBSONColumn( - originalBuffer, maxLength, validationVersion) - .isOK())) - return Status::OK(); - - return ColumnValidator<true>::doValidateBSONColumn( - originalBuffer, maxLength, validationVersion); - } else { - return ColumnValidator<false>::doValidateBSONColumn( - originalBuffer, maxLength, validationVersion); - } -} } // namespace -Status validateBSON(const char* originalBuffer, - uint64_t maxLength, - ValidationVersion validationVersion) noexcept { +Status validateBSON(const char* originalBuffer, uint64_t maxLength) noexcept { // First try validating using the fast but less precise version. That version will return // a not-OK status for objects with CodeWScope or nesting exceeding 32 levels. These cases and // actual failures will rerun the precise version that gives a detailed error context. - if (MONGO_likely( - ValidateBuffer<false>(originalBuffer, maxLength, validationVersion).validate().isOK())) + if (MONGO_likely(ValidateBuffer<false>(originalBuffer, maxLength).validate().isOK())) return Status::OK(); - return ValidateBuffer<true>(originalBuffer, maxLength, validationVersion).validate(); + return ValidateBuffer<true>(originalBuffer, maxLength).validate(); } - -Status validateBSON(const BSONObj& obj, ValidationVersion validationVersion) noexcept { - return validateBSON(obj.objdata(), obj.objsize(), validationVersion); -} - -Status validateBSONColumn(const char* originalBuffer, - int maxLength, - ValidationVersion validationVersion) noexcept { - return _doValidateColumn<true>(originalBuffer, maxLength, validationVersion); -} - } // namespace mongo |
