From cc774a291b08216b01eb14ad57fd40b8899b6532 Mon Sep 17 00:00:00 2001 From: Matt Broadstone Date: Wed, 16 Mar 2022 22:35:38 +0000 Subject: SERVER-64577: Disallow wTags write concern in FCV less than 5.3 also fixes SERVER-63100 --- src/mongo/db/SConscript | 9 ++- .../db/read_write_concern_provenance_base.idl | 11 +++ src/mongo/db/write_concern_options.cpp | 66 +++++++++++++++++- src/mongo/db/write_concern_options.h | 9 ++- src/mongo/db/write_concern_options.idl | 62 ++++++++++++++++- src/mongo/db/write_concern_options_test.cpp | 12 ++++ src/mongo/idl/basic_types.h | 57 ---------------- src/mongo/idl/basic_types.idl | 78 ++-------------------- src/mongo/idl/idl_test.cpp | 1 + 9 files changed, 169 insertions(+), 136 deletions(-) (limited to 'src/mongo') diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index c3152e8c8bf..19b521180e7 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -452,15 +452,18 @@ env.Library( ) env.Library( - target="write_concern_options", + target='write_concern_options', source=[ - "write_concern_options.cpp", + 'write_concern_options.cpp', + 'write_concern_options.idl', ], LIBDEPS=[ '$BUILD_DIR/mongo/bson/util/bson_extract', - '$BUILD_DIR/mongo/idl/basic_types', 'read_write_concern_provenance', ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/server_options_core', # For serverGlobalParams + ] ) env.Library( diff --git a/src/mongo/db/read_write_concern_provenance_base.idl b/src/mongo/db/read_write_concern_provenance_base.idl index 4695d5f0a34..017b89a6984 100644 --- a/src/mongo/db/read_write_concern_provenance_base.idl +++ b/src/mongo/db/read_write_concern_provenance_base.idl @@ -32,6 +32,17 @@ global: imports: - "mongo/idl/basic_types.idl" +enums: + ReadWriteConcernProvenanceSource: + description: "Provenance sources" + type: string + values: + clientSupplied: "clientSupplied" + implicitDefault: "implicitDefault" + customDefault: "customDefault" + getLastErrorDefaults: "getLastErrorDefaults" + internalWriteDefault: "internalWriteDefault" + structs: ReadWriteConcernProvenanceBase: description: "Represents the 'provenance' (ie. original source) of a read or write concern." diff --git a/src/mongo/db/write_concern_options.cpp b/src/mongo/db/write_concern_options.cpp index 4f1822884fa..c87e21691fc 100644 --- a/src/mongo/db/write_concern_options.cpp +++ b/src/mongo/db/write_concern_options.cpp @@ -32,13 +32,13 @@ #include #include "mongo/db/write_concern_options.h" +#include "mongo/db/write_concern_options_gen.h" #include "mongo/base/status.h" #include "mongo/base/string_data.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/db/field_parser.h" #include "mongo/db/repl/repl_set_config.h" -#include "mongo/idl/basic_types_gen.h" #include "mongo/util/str.h" namespace mongo { @@ -176,6 +176,70 @@ StatusWith WriteConcernOptions::extractWCFromCommand(const return parse(writeConcernObj); } +WriteConcernW deserializeWriteConcernW(BSONElement wEl) { + // Preserve pre-5.3 behavior to avoid issues with mixed-version clusters. This will eventually + // be removed when we release 7.0. + if (serverGlobalParams.featureCompatibility.isVersionInitialized() && + serverGlobalParams.featureCompatibility.isLessThan( + multiversion::FeatureCompatibilityVersion::kVersion_5_3)) { + uassert(ErrorCodes::FailedToParse, + "w has to be a number or string; found: {}"_format(typeName(wEl.type())), + wEl.type() != BSONType::Object); + } + + if (wEl.isNumber()) { + auto wNum = wEl.safeNumberLong(); + if (wNum < 0 || wNum > static_cast(repl::ReplSetConfig::kMaxMembers)) { + uasserted(ErrorCodes::FailedToParse, + "w has to be a non-negative number and not greater than {}; found: {}"_format( + repl::ReplSetConfig::kMaxMembers, wNum)); + } + + return WriteConcernW{wNum}; + } else if (wEl.type() == BSONType::String) { + return WriteConcernW{wEl.str()}; + } else if (wEl.type() == BSONType::Object) { + auto wTags = wEl.Obj(); + uassert(ErrorCodes::FailedToParse, "tagged write concern requires tags", !wTags.isEmpty()); + + WTags tags; + for (auto e : wTags) { + uassert( + ErrorCodes::FailedToParse, + "tags must be a single level document with only number values; found: {}"_format( + e.toString()), + e.isNumber()); + + tags.try_emplace(e.fieldName(), e.safeNumberInt()); + } + + return WriteConcernW{std::move(tags)}; + } else if (wEl.eoo() || wEl.type() == BSONType::jstNULL || wEl.type() == BSONType::Undefined) { + return WriteConcernW{}; + } + uasserted(ErrorCodes::FailedToParse, + "w has to be a number, string, or object; found: {}"_format(typeName(wEl.type()))); +} + +void serializeWriteConcernW(const WriteConcernW& w, StringData fieldName, BSONObjBuilder* builder) { + stdx::visit( + visit_helper::Overloaded{[&](int64_t wNumNodes) { + builder->appendNumber(fieldName, + static_cast(wNumNodes)); + }, + [&](std::string wMode) { builder->append(fieldName, wMode); }, + [&](WTags wTags) { builder->append(fieldName, wTags); }}, + w); +} + +std::int64_t parseWTimeoutFromBSON(BSONElement element) { + constexpr std::array validTypes{ + NumberLong, NumberInt, NumberDecimal, NumberDouble}; + bool isValidType = std::any_of( + validTypes.begin(), validTypes.end(), [&](auto type) { return element.type() == type; }); + return isValidType ? element.safeNumberLong() : 0; +} + BSONObj WriteConcernOptions::toBSON() const { BSONObjBuilder builder; serializeWriteConcernW(w, "w", &builder); diff --git a/src/mongo/db/write_concern_options.h b/src/mongo/db/write_concern_options.h index 4478aa173fe..e1525ec29f3 100644 --- a/src/mongo/db/write_concern_options.h +++ b/src/mongo/db/write_concern_options.h @@ -33,12 +33,14 @@ #include "mongo/db/jsobj.h" #include "mongo/db/read_write_concern_provenance.h" -#include "mongo/idl/basic_types_gen.h" namespace mongo { class Status; +using WTags = StringMap; +using WriteConcernW = stdx::variant; + struct WriteConcernOptions { public: enum class SyncMode { UNSET, NONE, FSYNC, JOURNAL }; @@ -175,4 +177,9 @@ private: ReadWriteConcernProvenance _provenance; }; +// Helpers for IDL parsing +WriteConcernW deserializeWriteConcernW(BSONElement wEl); +void serializeWriteConcernW(const WriteConcernW& w, StringData fieldName, BSONObjBuilder* builder); +std::int64_t parseWTimeoutFromBSON(BSONElement element); + } // namespace mongo diff --git a/src/mongo/db/write_concern_options.idl b/src/mongo/db/write_concern_options.idl index 07c3cdbffc4..8f9c595f24e 100644 --- a/src/mongo/db/write_concern_options.idl +++ b/src/mongo/db/write_concern_options.idl @@ -33,7 +33,7 @@ global: imports: - "mongo/idl/basic_types.idl" - + - "mongo/db/read_write_concern_provenance_base.idl" types: WriteConcern: @@ -42,3 +42,63 @@ types: cpp_type: "mongo::WriteConcernOptions" serializer: "mongo::WriteConcernOptions::toBSON" deserializer: "mongo::WriteConcernOptions::deserializerForIDL" + + writeConcernW: + bson_serialization_type: any + description: >- + A string or integer representing the 'w' option in a document specifying write concern. + See https://docs.mongodb.com/manual/reference/write-concern/" + cpp_type: "mongo::WriteConcernW" + serializer: "::mongo::serializeWriteConcernW" + deserializer: "::mongo::deserializeWriteConcernW" + + writeConcernWTimeout: + bson_serialization_type: any + description: >- + An integer representing the 'wtimeout' option in a document specifying write concern. + See https://docs.mongodb.com/manual/reference/write-concern/" + cpp_type: std::int64_t + deserializer: "::mongo::parseWTimeoutFromBSON" + +structs: + WriteConcernIdl: + description: "WriteConcern object parser" + strict: true + fields: + w: + type: writeConcernW + cpp_name: writeConcernW + optional: true + unstable: false + j: + type: safeBool + optional: true + unstable: false + wtimeout: + type: writeConcernWTimeout + default: 0 + unstable: false + fsync: + type: safeBool + optional: true + unstable: false + # Fields with names wElectionId, wOpTime, and getLastError are accepted in the WriteConcern document for + # backwards-compatibility reasons, but their values are entirely ignored. + wElectionId: + type: any + ignore: true + unstable: false + wOpTime: + type: any + ignore: true + unstable: false + getLastError: + type: any + ignore: true + unstable: false + provenance: + description: "The source for this provenance" + cpp_name: source + type: ReadWriteConcernProvenanceSource + optional: true + unstable: false diff --git a/src/mongo/db/write_concern_options_test.cpp b/src/mongo/db/write_concern_options_test.cpp index e9a3d111a08..abce6fa331e 100644 --- a/src/mongo/db/write_concern_options_test.cpp +++ b/src/mongo/db/write_concern_options_test.cpp @@ -187,6 +187,10 @@ TEST(WriteConcernOptionsTest, ParseWithTags) { ASSERT_STRING_CONTAINS(status.reason(), "tags must be a single level document with only number values"); + status = WriteConcernOptions::parse(BSON("w" << BSONObj())).getStatus(); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_STRING_CONTAINS(status.reason(), "tagged write concern requires tags"); + auto sw = WriteConcernOptions::parse(BSON("w" << BSON("abc" << 1))); ASSERT_OK(sw.getStatus()); @@ -209,5 +213,13 @@ TEST(WriteConcernOptionsTest, ParseWithTags) { ASSERT(wc != wc5); } +TEST(WriteConcernOptionsTest, WTagsNotPermittedFCVLessThan53) { + serverGlobalParams.mutableFeatureCompatibility.setVersion( + multiversion::FeatureCompatibilityVersion::kVersion_5_2); + auto status = WriteConcernOptions::parse(BSON("w" << BSON("abc" << 1))).getStatus(); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_STRING_CONTAINS(status.reason(), "w has to be a number or string"); +} + } // namespace } // namespace mongo diff --git a/src/mongo/idl/basic_types.h b/src/mongo/idl/basic_types.h index ccc357630a5..8e0a45d874d 100644 --- a/src/mongo/idl/basic_types.h +++ b/src/mongo/idl/basic_types.h @@ -187,61 +187,4 @@ public: private: BSONObj _obj; }; - -using WTags = StringMap; -using WriteConcernW = stdx::variant; - -inline static const int64_t kMaxMembers = 50; -inline WriteConcernW deserializeWriteConcernW(BSONElement wEl) { - if (wEl.isNumber()) { - auto wNum = wEl.safeNumberLong(); - if (wNum < 0 || wNum > kMaxMembers) { - uasserted(ErrorCodes::FailedToParse, - "w has to be a non-negative number and not greater than {}; found: {}"_format( - kMaxMembers, wNum)); - } - - return WriteConcernW{wNum}; - } else if (wEl.type() == BSONType::String) { - return WriteConcernW{wEl.str()}; - } else if (wEl.type() == BSONType::Object) { - WTags tags; - for (auto e : wEl.Obj()) { - uassert( - ErrorCodes::FailedToParse, - "tags must be a single level document with only number values; found: {}"_format( - e.toString()), - e.isNumber()); - - tags.try_emplace(e.fieldName(), e.safeNumberInt()); - } - - return WriteConcernW{std::move(tags)}; - } else if (wEl.eoo() || wEl.type() == BSONType::jstNULL || wEl.type() == BSONType::Undefined) { - return WriteConcernW{}; - } - uasserted(ErrorCodes::FailedToParse, - "w has to be a number, string, or object; found: {}"_format(typeName(wEl.type()))); -} - -inline void serializeWriteConcernW(const WriteConcernW& w, - StringData fieldName, - BSONObjBuilder* builder) { - stdx::visit( - visit_helper::Overloaded{[&](int64_t wNumNodes) { - builder->appendNumber(fieldName, - static_cast(wNumNodes)); - }, - [&](std::string wMode) { builder->append(fieldName, wMode); }, - [&](WTags wTags) { builder->append(fieldName, wTags); }}, - w); -} - -inline std::int64_t parseWTimeoutFromBSON(BSONElement element) { - constexpr std::array validTypes{ - NumberLong, NumberInt, NumberDecimal, NumberDouble}; - bool isValidType = std::any_of( - validTypes.begin(), validTypes.end(), [&](auto type) { return element.type() == type; }); - return isValidType ? element.safeNumberLong() : 0; -} } // namespace mongo diff --git a/src/mongo/idl/basic_types.idl b/src/mongo/idl/basic_types.idl index d17b87ded65..b635ca97b0e 100644 --- a/src/mongo/idl/basic_types.idl +++ b/src/mongo/idl/basic_types.idl @@ -240,7 +240,7 @@ types: cpp_type: "mongo::IDLAnyType" serializer: mongo::IDLAnyType::serializeToBSON deserializer: mongo::IDLAnyType::parseFromBSON - + IDLAnyTypeOwned: bson_serialization_type: any description: "Holds a BSONElement of any type. Does not require the backing BSON to stay @@ -249,34 +249,7 @@ types: serializer: mongo::IDLAnyTypeOwned::serializeToBSON deserializer: mongo::IDLAnyTypeOwned::parseFromBSON - writeConcernW: - bson_serialization_type: any - description: >- - A string or integer representing the 'w' option in a document specifying write concern. - See https://docs.mongodb.com/manual/reference/write-concern/" - cpp_type: "mongo::WriteConcernW" - serializer: "::mongo::serializeWriteConcernW" - deserializer: "::mongo::deserializeWriteConcernW" - - writeConcernWTimeout: - bson_serialization_type: any - description: >- - An integer representing the 'wtimeout' option in a document specifying write concern. - See https://docs.mongodb.com/manual/reference/write-concern/" - cpp_type: std::int64_t - deserializer: "mongo::parseWTimeoutFromBSON" - enums: - ReadWriteConcernProvenanceSource: - description: "Provenance sources" - type: string - values: - clientSupplied: "clientSupplied" - implicitDefault: "implicitDefault" - customDefault: "customDefault" - getLastErrorDefaults: "getLastErrorDefaults" - internalWriteDefault: "internalWriteDefault" - CollationCaseFirst: description: Sort order of case differences. type: string @@ -319,16 +292,16 @@ structs: strict: false fields: ok: - type: safeDouble + type: safeDouble validator: { gte: 0.0, lte: 0.0 } unstable: false - code: + code: type: int unstable: false - codeName: + codeName: type: string unstable: false - errmsg: + errmsg: type: string unstable: false errorLabels: @@ -345,47 +318,6 @@ structs: optional: true validator: { gte: 0 } unstable: false - WriteConcernIdl: - description: "WriteConcern object parser" - strict: true - fields: - w: - type: writeConcernW - cpp_name: writeConcernW - optional: true - unstable: false - j: - type: safeBool - optional: true - unstable: false - wtimeout: - type: writeConcernWTimeout - default: 0 - unstable: false - fsync: - type: safeBool - optional: true - unstable: false - # Fields with names wElectionId, wOpTime, and getLastError are accepted in the WriteConcern document for - # backwards-compatibility reasons, but their values are entirely ignored. - wElectionId: - type: any - ignore: true - unstable: false - wOpTime: - type: any - ignore: true - unstable: false - getLastError: - type: any - ignore: true - unstable: false - provenance: - description: "The source for this provenance" - cpp_name: source - type: ReadWriteConcernProvenanceSource - optional: true - unstable: false Collation: description: "Specifies the default collation for the collection or the view." diff --git a/src/mongo/idl/idl_test.cpp b/src/mongo/idl/idl_test.cpp index febbe3dbc3f..505c99f756d 100644 --- a/src/mongo/idl/idl_test.cpp +++ b/src/mongo/idl/idl_test.cpp @@ -38,6 +38,7 @@ #include "mongo/bson/oid.h" #include "mongo/db/auth/authorization_contract.h" #include "mongo/db/auth/resource_pattern.h" +#include "mongo/db/write_concern_options_gen.h" #include "mongo/idl/unittest_gen.h" #include "mongo/rpc/op_msg.h" #include "mongo/unittest/bson_test_util.h" -- cgit v1.2.1