diff --git a/src/brpc/couchbase.cpp b/src/brpc/couchbase.cpp index fd26107a41..7e5fdfe5f3 100644 --- a/src/brpc/couchbase.cpp +++ b/src/brpc/couchbase.cpp @@ -50,6 +50,17 @@ constexpr uint32_t APPLE_VBUCKET_COUNT ALLOW_UNUSED = 64; constexpr uint32_t DEFAULT_VBUCKET_COUNT ALLOW_UNUSED = 1024; constexpr int CONNECTION_ID_SIZE = 33; constexpr size_t RANDOM_ID_HEX_SIZE = 67; // 33 bytes * 2 + null terminator + +// Bound nesting before building the DOM, whose destruction is recursive. +struct ManifestDepthHandler + : BUTIL_RAPIDJSON_NAMESPACE::BaseReaderHandler< + BUTIL_RAPIDJSON_NAMESPACE::UTF8<>, ManifestDepthHandler> { + unsigned depth = 0; + bool StartObject() { return ++depth <= 100; } + bool StartArray() { return ++depth <= 100; } + bool EndObject(BUTIL_RAPIDJSON_NAMESPACE::SizeType) { --depth; return true; } + bool EndArray(BUTIL_RAPIDJSON_NAMESPACE::SizeType) { --depth; return true; } +}; } // namespace // Static member definitions @@ -124,9 +135,16 @@ bool CouchbaseManifestManager::jsonToCollectionManifest( return false; } - // Parse JSON using RapidJSON + BUTIL_RAPIDJSON_NAMESPACE::Reader reader; + BUTIL_RAPIDJSON_NAMESPACE::StringStream stream(json.c_str()); + ManifestDepthHandler handler; + if (!reader.Parse(stream, handler) + || stream.Tell() != json.size()) { + return false; + } + BUTIL_RAPIDJSON_NAMESPACE::Document document; - document.Parse(json.c_str()); + document.Parse(json.c_str()); if (document.HasParseError()) { DEBUG_PRINT("Failed to parse JSON: " << document.GetParseError()); diff --git a/test/brpc_couchbase_unittest.cpp b/test/brpc_couchbase_unittest.cpp index f6c819089b..f96df14403 100644 --- a/test/brpc_couchbase_unittest.cpp +++ b/test/brpc_couchbase_unittest.cpp @@ -40,6 +40,24 @@ namespace { // Unit Tests - No Server Required class CouchbaseUnitTest : public testing::Test {}; +TEST_F(CouchbaseUnitTest, ManifestJsonNesting) { + brpc::CouchbaseManifestManager manager; + brpc::CouchbaseManifestManager::CollectionManifest manifest; + const std::string prefix = "{\"uid\":\"1\",\"scopes\":[],\"extra\":"; + for (size_t depth : {size_t(99), size_t(100), size_t(20000)}) { + const std::string json = prefix + std::string(depth, '[') + "0" + + std::string(depth, ']') + "}"; + EXPECT_EQ(depth < 100, manager.jsonToCollectionManifest(json, &manifest)) + << "depth=" << depth; + } + EXPECT_TRUE(manager.jsonToCollectionManifest( + "{\"uid\":\"1\",\"scopes\":[],\"extra\":\"[\\\"{]\"}", &manifest)); + std::string with_nul = "{\"uid\":\"1\",\"scopes\":[]}"; + with_nul.push_back('\0'); + with_nul += "[]"; + EXPECT_FALSE(manager.jsonToCollectionManifest(with_nul, &manifest)); +} + TEST_F(CouchbaseUnitTest, RejectOversizedResponseBeforeBufferingBody) { GFLAGS_NAMESPACE::FlagSaver flag_saver; brpc::FLAGS_max_body_size = 1024;