diff options
author | Bernard Gorman <bernard.gorman@gmail.com> | 2018-04-02 06:18:06 +0100 |
---|---|---|
committer | Charlie Swanson <charlie.swanson@mongodb.com> | 2018-06-14 10:05:52 -0400 |
commit | 66bf88e8535b94e91c927832ffaf60c5f0f465b9 (patch) | |
tree | 5b585ce9526df5176ddb794d52b4a998a721345c | |
parent | d27aa4e8ebb950cce072f188d732958aec4bd083 (diff) | |
download | mongo-66bf88e8535b94e91c927832ffaf60c5f0f465b9.tar.gz |
SERVER-34040 Disallow $changeStream on internal databases and system collections
(cherry picked from commit 718c71966f7a9350cd747604409cd0adb913fb5e)
-rw-r--r-- | jstests/change_streams/invalid_namespaces.js | 28 | ||||
-rw-r--r-- | jstests/libs/change_stream_util.js | 21 | ||||
-rw-r--r-- | src/mongo/db/namespace_string.h | 3 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_change_stream.cpp | 11 |
4 files changed, 63 insertions, 0 deletions
diff --git a/jstests/change_streams/invalid_namespaces.js b/jstests/change_streams/invalid_namespaces.js new file mode 100644 index 00000000000..ed1c98af9f7 --- /dev/null +++ b/jstests/change_streams/invalid_namespaces.js @@ -0,0 +1,28 @@ +// Tests that the namespace being watched cannot be a system namespace. +// Mark as assumes_read_preference_unchanged since reading from the non-replicated "system.profile" +// collection results in a failure in the secondary reads suite. +// @tags: [assumes_read_preference_unchanged] +(function() { + "use strict"; + + load("jstests/libs/fixture_helpers.js"); // For 'FixtureHelpers'. + load("jstests/libs/change_stream_util.js"); // For assert[Valid|Invalid]ChangeStreamNss. + + // Test that a change stream cannot be opened on the "admin", "config", or "local" databases. + assertInvalidChangeStreamNss("admin"); + assertInvalidChangeStreamNss("config"); + // Not allowed to access 'local' database through mongos. + if (!FixtureHelpers.isMongos()) { + assertInvalidChangeStreamNss("local"); + } + + // Test that a change stream cannot be opened on 'system.' collections. + assertInvalidChangeStreamNss("test", "system.users"); + assertInvalidChangeStreamNss("test", "system.profile"); + assertInvalidChangeStreamNss("test", "system.version"); + + // Test that a change stream can be opened on namespaces with 'system' in the name, but not + // considered an internal 'system dot' namespace. + assertValidChangeStreamNss("test", "systemindexes"); + assertValidChangeStreamNss("test", "system_users"); +}()); diff --git a/jstests/libs/change_stream_util.js b/jstests/libs/change_stream_util.js index aea8aa3a1a5..75821d7e3d3 100644 --- a/jstests/libs/change_stream_util.js +++ b/jstests/libs/change_stream_util.js @@ -205,3 +205,24 @@ ChangeStreamTest.assertChangeStreamThrowsCode = function assertChangeStreamThrow } assert(false, "expected this to be unreachable"); }; + +/** + * A set of functions to help validate the behaviour of $changeStreams for a given namespace. + */ +function assertChangeStreamNssBehaviour(dbName, collName = "test", assertFunc) { + const testDb = db.getSiblingDB(dbName); + // Make sure the database exists to ensure we get the right error code. + assert.writeOK(testDb.unrelated_collection_to_create_db.insert({})); + const res = + testDb.runCommand({aggregate: collName, pipeline: [{$changeStream: {}}], cursor: {}}); + return assertFunc(res); +} +function assertValidChangeStreamNss(dbName, collName = "test") { + const res = assertChangeStreamNssBehaviour(dbName, collName, assert.commandWorked); + assert.commandWorked( + db.getSiblingDB(dbName).runCommand({killCursors: collName, cursors: [res.cursor.id]})); +} +function assertInvalidChangeStreamNss(dbName, collName = "test") { + assertChangeStreamNssBehaviour( + dbName, collName, (res) => assert.commandFailedWithCode(res, ErrorCodes.InvalidNamespace)); +} diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index 07240fa9f0a..5347f4c23b6 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -180,6 +180,9 @@ public: bool isSystem() const { return coll().startsWith("system."); } + bool isAdminDB() const { + return db() == kAdminDb; + } bool isLocal() const { return db() == "local"; } diff --git a/src/mongo/db/pipeline/document_source_change_stream.cpp b/src/mongo/db/pipeline/document_source_change_stream.cpp index 35046707be6..56e8fc86e08 100644 --- a/src/mongo/db/pipeline/document_source_change_stream.cpp +++ b/src/mongo/db/pipeline/document_source_change_stream.cpp @@ -308,6 +308,17 @@ list<intrusive_ptr<DocumentSource>> DocumentSourceChangeStream::createFromBson( intrusive_ptr<DocumentSource> resumeStage = nullptr; auto spec = DocumentSourceChangeStreamSpec::parse(IDLParserErrorContext("$changeStream"), elem.embeddedObject()); + + uassert(ErrorCodes::InvalidNamespace, + str::stream() << "$changeStream may not be opened on the internal " << expCtx->ns.db() + << " database", + !(expCtx->ns.isAdminDB() || expCtx->ns.isLocal() || expCtx->ns.isConfigDB())); + + uassert(ErrorCodes::InvalidNamespace, + str::stream() << "$changeStream may not be opened on the internal " << expCtx->ns.ns() + << " collection", + !expCtx->ns.isSystem()); + if (auto resumeAfter = spec.getResumeAfter()) { ResumeToken token = resumeAfter.get(); ResumeTokenData tokenData = token.getData(); |