summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2018-04-02 06:18:06 +0100
committerCharlie Swanson <charlie.swanson@mongodb.com>2018-06-14 10:05:52 -0400
commit66bf88e8535b94e91c927832ffaf60c5f0f465b9 (patch)
tree5b585ce9526df5176ddb794d52b4a998a721345c
parentd27aa4e8ebb950cce072f188d732958aec4bd083 (diff)
downloadmongo-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.js28
-rw-r--r--jstests/libs/change_stream_util.js21
-rw-r--r--src/mongo/db/namespace_string.h3
-rw-r--r--src/mongo/db/pipeline/document_source_change_stream.cpp11
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();