summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuhong Zhang <yuhong.zhang@mongodb.com>2022-09-01 19:16:42 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-09-01 20:38:13 +0000
commit8834bd03c22a3a0d21227def26d7a474dafaabc8 (patch)
treec6ea3dc392e8575782d9210745ab923b5474cb8a
parentd2979f3ee500ed4dfd5f67fe2c9412f44c9ceb62 (diff)
downloadmongo-8834bd03c22a3a0d21227def26d7a474dafaabc8.tar.gz
SERVER-67879 Check encrypted BSON value's structural consistency
-rw-r--r--jstests/disk/validate_bson_inconsistency.js42
-rw-r--r--src/mongo/bson/bson_validate.cpp54
-rw-r--r--src/mongo/bson/bson_validate_test.cpp55
-rw-r--r--src/mongo/crypto/encryption_fields_util.h15
-rw-r--r--src/mongo/crypto/fle_crypto.h5
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,