diff options
-rw-r--r-- | jstests/disk/validate_bson_inconsistency.js | 42 | ||||
-rw-r--r-- | src/mongo/bson/bson_validate.cpp | 54 | ||||
-rw-r--r-- | src/mongo/bson/bson_validate_test.cpp | 55 | ||||
-rw-r--r-- | src/mongo/crypto/encryption_fields_util.h | 15 | ||||
-rw-r--r-- | src/mongo/crypto/fle_crypto.h | 5 |
5 files changed, 171 insertions, 0 deletions
diff --git a/jstests/disk/validate_bson_inconsistency.js b/jstests/disk/validate_bson_inconsistency.js index 663354b93c4..b7b03062a28 100644 --- a/jstests/disk/validate_bson_inconsistency.js +++ b/jstests/disk/validate_bson_inconsistency.js @@ -278,4 +278,46 @@ resetDbpath(dbpath); MongoRunner.stopMongod(mongod, null, {skipValidation: true}); })(); + +(function validateDocumentsInvalidEncryptedBSONValue() { + jsTestLog("Validate documents with invalid Encrypted BSON Value"); + + let mongod = startMongodOnExistingPath(dbpath); + let db = mongod.getDB(baseName); + const collName = collNamePrefix + count++; + + db.createCollection(collName); + let testColl = db[collName]; + // A valid Encrypted BSON document with the type byte, 16-byte key uuid, original BSON type + // byte, and an empty cipher text. + const properFLE = HexData(6, "060102030405060708091011121314151610"); + // Invalid Encrypted BSON Value subtype 3. + const improperFLE1 = HexData(6, "030102030405060708091011121314151610"); + // Invalid original BSON type MinKey. + const improperFLE2 = HexData(6, "0601020304050607080910111213141516ff"); + // Empty Encrypted BSON Value. + const improperFLE3 = HexData(6, ""); + // Short Encrypted BSON Value. + const improperFLE4 = HexData(6, "0601"); + + assert.commandWorked(testColl.insertMany([ + {"fle": properFLE}, + {"fle": improperFLE1}, + {"fle": improperFLE2}, + {"fle": improperFLE3}, + {"fle": improperFLE4}, + ])); + + let res = assert.commandWorked(testColl.validate()); + assert(res.valid, tojson(res)); + assert.eq(res.nNonCompliantDocuments, 4); + assert.eq(res.warnings.length, 1); + + res = assert.commandWorked(testColl.validate({checkBSONConformance: true})); + assert(res.valid, tojson(res)); + assert.eq(res.nNonCompliantDocuments, 4); + assert.eq(res.warnings.length, 1); + + MongoRunner.stopMongod(mongod, null, {skipValidation: true}); +})(); })(); diff --git a/src/mongo/bson/bson_validate.cpp b/src/mongo/bson/bson_validate.cpp index c1a4a21e34e..e7f9ae28550 100644 --- a/src/mongo/bson/bson_validate.cpp +++ b/src/mongo/bson/bson_validate.cpp @@ -36,6 +36,8 @@ #include "mongo/bson/bson_depth.h" #include "mongo/bson/bsonelement.h" #include "mongo/bson/util/bsoncolumn.h" +#include "mongo/crypto/encryption_fields_util.h" +#include "mongo/crypto/fle_field_schema_gen.h" #include "mongo/logv2/log.h" #include "mongo/util/str_escape.h" @@ -150,6 +152,10 @@ public: md5Size == md5Length); break; } + case BinDataType::Encrypt: { + _checkEncryptedBSONValue(ptr); + break; + } } break; } @@ -213,6 +219,54 @@ private: } } + void _checkEncryptedBSONValue(const char* ptr) { + constexpr uint32_t UUIDLength = 16; + constexpr uint32_t minLength = sizeof(uint8_t) + UUIDLength + sizeof(uint8_t); + + auto len = ConstDataView(ptr).read<LittleEndian<uint32_t>>(); + // Make sure we can read the subtype byte of the Encrypted BSON Value. + uassert(ErrorCodes::NonConformantBSON, + fmt::format("Invalid Encrypted BSON Value length {}", len), + len); + + // Skip the size bytes and BinData subtype byte to the actual encrypted data. + ptr += sizeof(uint32_t) + sizeof(uint8_t); + auto encryptedBinDataType = static_cast<EncryptedBinDataType>( + (uint8_t)ConstDataView(ptr).read<LittleEndian<uint8_t>>()); + // Only subtype 1, 2, 6, 7, and 9 can exist in MongoDB collections. + switch (encryptedBinDataType) { + case EncryptedBinDataType::kDeterministic: + case EncryptedBinDataType::kRandom: { + uassert(ErrorCodes::NonConformantBSON, + fmt::format("Invalid Encrypted BSON Value length {}", len), + len > minLength); + break; + } + case EncryptedBinDataType::kFLE2UnindexedEncryptedValue: + case EncryptedBinDataType::kFLE2EqualityIndexedValue: + case EncryptedBinDataType::kFLE2RangeIndexedValue: { + uassert(ErrorCodes::NonConformantBSON, + fmt::format("Invalid Encrypted BSON Value length {}", len), + len >= minLength); + auto originalBsonType = + static_cast<BSONType>((int8_t)ConstDataView(ptr + sizeof(uint8_t) + UUIDLength) + .read<LittleEndian<uint8_t>>()); + uassert(ErrorCodes::NonConformantBSON, + fmt::format( + "BSON type '{}' is not supported for Encrypted BSON Value subtype {}", + typeName(originalBsonType), + encryptedBinDataType), + isFLE2SupportedType(encryptedBinDataType, originalBsonType)); + break; + } + default: { + uasserted(ErrorCodes::NonConformantBSON, + fmt::format("Unsupported Encrypted BSON Value type {} in the collection", + encryptedBinDataType)); + } + } + } + protected: // Behaves like a stack, used to validate array index count. std::vector<Level> indexCount; diff --git a/src/mongo/bson/bson_validate_test.cpp b/src/mongo/bson/bson_validate_test.cpp index 8f4176bddc2..47c1486ff36 100644 --- a/src/mongo/bson/bson_validate_test.cpp +++ b/src/mongo/bson/bson_validate_test.cpp @@ -34,7 +34,9 @@ #include "mongo/bson/bson_depth.h" #include "mongo/bson/bson_validate.h" #include "mongo/bson/util/bsoncolumnbuilder.h" +#include "mongo/crypto/fle_field_schema_gen.h" #include "mongo/db/jsobj.h" +#include "mongo/db/matcher/expression_type.h" #include "mongo/logv2/log.h" #include "mongo/platform/random.h" #include "mongo/unittest/unittest.h" @@ -757,4 +759,57 @@ TEST(BSONValidateExtended, BSONColumn) { ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); } +TEST(BSONValidateExtended, BSONEncryptedValue) { + FleBlobHeader blob; + memset(blob.keyUUID, 0, sizeof(blob.keyUUID)); + blob.originalBsonType = BSONType::String; + blob.fleBlobSubtype = static_cast<int8_t>(EncryptedBinDataType::kFLE2UnindexedEncryptedValue); + auto fle = BSONBinData( + reinterpret_cast<const void*>(&blob), sizeof(FleBlobHeader), BinDataType::Encrypt); + BSONObj obj = BSON("a" << fle); + Status status = validateBSON(obj, BSONValidateMode::kExtended); + ASSERT_OK(status); + + // Empty Encrypted BSON Value. + auto emptyBinData = ""; + obj = BSON("a" << BSONBinData(emptyBinData, 0, Encrypt)); + status = validateBSON(obj, mongo::BSONValidateMode::kExtended); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + status = validateBSON(obj, mongo::BSONValidateMode::kFull); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + + // Encrypted BSON value subtype not supposed to persist. + blob.originalBsonType = BSONType::String; + blob.fleBlobSubtype = static_cast<int8_t>(EncryptedBinDataType::kFLE2Placeholder); + fle = BSONBinData( + reinterpret_cast<const void*>(&blob), sizeof(FleBlobHeader), BinDataType::Encrypt); + obj = BSON("a" << fle); + status = validateBSON(obj, mongo::BSONValidateMode::kExtended); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + status = validateBSON(obj, mongo::BSONValidateMode::kFull); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + + // Short Encrypted BSON Value. + blob.originalBsonType = BSONType::String; + blob.fleBlobSubtype = static_cast<int8_t>(EncryptedBinDataType::kFLE2UnindexedEncryptedValue); + fle = BSONBinData( + reinterpret_cast<const void*>(&blob), sizeof(FleBlobHeader) - 1, BinDataType::Encrypt); + obj = BSON("a" << fle); + status = validateBSON(obj, mongo::BSONValidateMode::kExtended); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + status = validateBSON(obj, mongo::BSONValidateMode::kFull); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + + // Unsupported original BSON subtype. + blob.originalBsonType = BSONType::MaxKey; + blob.fleBlobSubtype = static_cast<int8_t>(EncryptedBinDataType::kFLE2UnindexedEncryptedValue); + fle = BSONBinData( + reinterpret_cast<const void*>(&blob), sizeof(FleBlobHeader), BinDataType::Encrypt); + obj = BSON("a" << fle); + status = validateBSON(obj, mongo::BSONValidateMode::kExtended); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); + status = validateBSON(obj, mongo::BSONValidateMode::kFull); + ASSERT_EQ(status.code(), ErrorCodes::NonConformantBSON); +} + } // namespace diff --git a/src/mongo/crypto/encryption_fields_util.h b/src/mongo/crypto/encryption_fields_util.h index 0a67601d91f..521a4c736ab 100644 --- a/src/mongo/crypto/encryption_fields_util.h +++ b/src/mongo/crypto/encryption_fields_util.h @@ -34,6 +34,7 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsontypes.h" #include "mongo/crypto/encryption_fields_gen.h" +#include "mongo/crypto/fle_field_schema_gen.h" #include "mongo/db/field_ref.h" #include "mongo/util/assert_util.h" @@ -150,6 +151,20 @@ inline bool isFLE2UnindexedSupportedType(BSONType type) { } } +// Wrapper of the three helper functions above. Should only be used on FLE type 6, 7, and 9. +inline bool isFLE2SupportedType(EncryptedBinDataType fleType, BSONType bsonType) { + switch (fleType) { + case EncryptedBinDataType::kFLE2UnindexedEncryptedValue: + return isFLE2UnindexedSupportedType(bsonType); + case EncryptedBinDataType::kFLE2EqualityIndexedValue: + return isFLE2EqualityIndexedSupportedType(bsonType); + case EncryptedBinDataType::kFLE2RangeIndexedValue: + return isFLE2RangeIndexedSupportedType(bsonType); + default: + MONGO_UNREACHABLE; + } +} + struct EncryptedFieldMatchResult { FieldRef encryptedField; bool keyIsPrefixOrEqual; diff --git a/src/mongo/crypto/fle_crypto.h b/src/mongo/crypto/fle_crypto.h index 563e3e0154b..65178dcedb1 100644 --- a/src/mongo/crypto/fle_crypto.h +++ b/src/mongo/crypto/fle_crypto.h @@ -915,6 +915,8 @@ public: * uint8_t[32] esc; // ESCDerivedFromDataTokenAndContentionFactorToken * uint8_t[32] ecc; // ECCDerivedFromDataTokenAndContentionFactorToken *} + * + * The specification needs to be in sync with the validation in 'bson_validate.cpp'. */ struct FLE2IndexedEqualityEncryptedValue { FLE2IndexedEqualityEncryptedValue(FLE2InsertUpdatePayload payload, uint64_t counter); @@ -958,6 +960,7 @@ struct FLE2IndexedEqualityEncryptedValue { * ciphertext[ciphertext_length]; * } blob; * + * The specification needs to be in sync with the validation in 'bson_validate.cpp'. */ struct FLE2UnindexedEncryptedValue { static std::vector<uint8_t> serialize(const FLEUserKeyAndId& userKey, @@ -1000,6 +1003,8 @@ struct FLEEdgeToken { * uint8_t[32] ecc; // ECCDerivedFromDataTokenAndContentionFactorToken * } edges[edgeCount]; *} + * + * The specification needs to be in sync with the validation in 'bson_validate.cpp'. */ struct FLE2IndexedRangeEncryptedValue { FLE2IndexedRangeEncryptedValue(FLE2InsertUpdatePayload payload, |