diff options
author | rahuldhodapkar <rahul.m.dhodapkar@gmail.com> | 2016-01-21 10:30:48 -0500 |
---|---|---|
committer | rahuldhodapkar <rahul.m.dhodapkar@gmail.com> | 2016-02-17 17:17:54 -0500 |
commit | 054d07ae6bcbc4eb408c1d7ba070b4dccdaa9cd3 (patch) | |
tree | 5a76c2ea69431d63072830f572558f2cbe272e01 /src | |
parent | c666c99bef83a238f0853db3249f5a8543581b4f (diff) | |
download | mongo-054d07ae6bcbc4eb408c1d7ba070b4dccdaa9cd3.tar.gz |
SERVER-9131 prevent automatic function marshalling in shell
add shell flag --enableJavaScriptProtection and server setParameter javascriptProtection
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/scripting/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/scripting/engine.h | 3 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/code.cpp | 96 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/code.h | 55 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/engine.cpp | 9 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/engine.h | 3 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.cpp | 6 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.h | 10 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/internedstring.defs | 2 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/valuereader.cpp | 32 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/valuewriter.cpp | 21 | ||||
-rw-r--r-- | src/mongo/shell/db.js | 7 | ||||
-rw-r--r-- | src/mongo/shell/dbshell.cpp | 1 | ||||
-rw-r--r-- | src/mongo/shell/shell_options.cpp | 8 | ||||
-rw-r--r-- | src/mongo/shell/shell_options.h | 1 |
15 files changed, 249 insertions, 6 deletions
diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index 4f92296f1c7..c27e780985a 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -107,6 +107,7 @@ if usemozjs: 'mozjs/base.cpp', 'mozjs/bindata.cpp', 'mozjs/bson.cpp', + 'mozjs/code.cpp', 'mozjs/countdownlatch.cpp', 'mozjs/cursor.cpp', 'mozjs/cursor_handle.cpp', diff --git a/src/mongo/scripting/engine.h b/src/mongo/scripting/engine.h index dcceb9c07ff..f06333e9a45 100644 --- a/src/mongo/scripting/engine.h +++ b/src/mongo/scripting/engine.h @@ -248,6 +248,9 @@ public: virtual void enableJIT(bool value) = 0; virtual bool isJITEnabled() const = 0; + virtual void enableJavaScriptProtection(bool value) = 0; + virtual bool isJavaScriptProtectionEnabled() const = 0; + static void setup(); static void dropScopeCache(); diff --git a/src/mongo/scripting/mozjs/code.cpp b/src/mongo/scripting/mozjs/code.cpp new file mode 100644 index 00000000000..701ed1d0375 --- /dev/null +++ b/src/mongo/scripting/mozjs/code.cpp @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/code.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/scripting/mozjs/wrapconstrainedmethod.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec CodeInfo::methods[2] = { + MONGO_ATTACH_JS_CONSTRAINED_METHOD(toString, CodeInfo), + JS_FS_END, +}; + +const char* const CodeInfo::className = "Code"; + +void CodeInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) { + ObjectWrapper o(cx, args.thisv()); + + std::string str = str::stream() + << "Code({\"code\":\"" << o.getString(InternedString::code) << "\"," + << "\"scope\":" << o.getObject(InternedString::scope) << "\"})"; + + ValueReader(cx, args.rval()).fromStringData(str); +} + +void CodeInfo::construct(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::BadValue, + "Code needs 0, 1 or 2 arguments", + args.length() == 0 || args.length() == 1 || args.length() == 2); + auto scope = getScope(cx); + + JS::RootedObject thisv(cx); + + scope->getProto<CodeInfo>().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + if (args.length() == 0) { + o.setString(InternedString::code, ""); + } + else if (args.length() == 1) { + JS::HandleValue codeArg = args.get(0); + if (!codeArg.isString()) + uasserted(ErrorCodes::BadValue, "code must be a string"); + + o.setValue(InternedString::code, codeArg); + } + else { + if (!args.get(0).isString()) + uasserted(ErrorCodes::BadValue, "code must be a string"); + if (!args.get(1).isObject()) + uasserted(ErrorCodes::BadValue, "scope must be an object"); + + o.setValue(InternedString::code, args.get(0)); + o.setValue(InternedString::scope, args.get(1)); + } + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/code.h b/src/mongo/scripting/mozjs/code.h new file mode 100644 index 00000000000..a9d2e5523bb --- /dev/null +++ b/src/mongo/scripting/mozjs/code.h @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "Code" Javascript object. + * + * Holds a bson Code or CodeWScope + */ + +struct CodeInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DECLARE_JS_FUNCTION(toString); + }; + + static const JSFunctionSpec methods[2]; + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/engine.cpp b/src/mongo/scripting/mozjs/engine.cpp index ac4691ddd4e..54c6c5555e6 100644 --- a/src/mongo/scripting/mozjs/engine.cpp +++ b/src/mongo/scripting/mozjs/engine.cpp @@ -47,6 +47,7 @@ namespace mongo { namespace { MONGO_EXPORT_SERVER_PARAMETER(disableJavaScriptJIT, bool, false); +MONGO_EXPORT_SERVER_PARAMETER(javascriptProtection, bool, false); } // namespace @@ -126,6 +127,14 @@ bool MozJSScriptEngine::isJITEnabled() const { return !disableJavaScriptJIT.load(); } +void MozJSScriptEngine::enableJavaScriptProtection(bool value) { + javascriptProtection.store(value); +} + +bool MozJSScriptEngine::isJavaScriptProtectionEnabled() const { + return javascriptProtection.load(); +} + void MozJSScriptEngine::registerOperation(OperationContext* txn, MozJSImplScope* scope) { stdx::lock_guard<stdx::mutex> giLock(_globalInterruptLock); diff --git a/src/mongo/scripting/mozjs/engine.h b/src/mongo/scripting/mozjs/engine.h index 80d2ee8ec0f..9b3178912e7 100644 --- a/src/mongo/scripting/mozjs/engine.h +++ b/src/mongo/scripting/mozjs/engine.h @@ -66,6 +66,9 @@ public: void enableJIT(bool value) override; bool isJITEnabled() const override; + void enableJavaScriptProtection(bool value) override; + bool isJavaScriptProtectionEnabled() const override; + void registerOperation(OperationContext* ctx, MozJSImplScope* scope); void unregisterOperation(unsigned int opId); diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index 52e3af75ca6..9c298b4e52c 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -169,6 +169,10 @@ OperationContext* MozJSImplScope::getOpContext() const { return _opCtx; } +bool MozJSImplScope::isJavaScriptProtectionEnabled() const { + return _engine->isJavaScriptProtectionEnabled(); +} + bool MozJSImplScope::_interruptCallback(JSContext* cx) { auto scope = getScope(cx); @@ -300,6 +304,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) _hasOutOfMemoryException(false), _binDataProto(_context), _bsonProto(_context), + _codeProto(_context), _countDownLatchProto(_context), _cursorProto(_context), _cursorHandleProto(_context), @@ -738,6 +743,7 @@ void MozJSImplScope::reset() { void MozJSImplScope::installBSONTypes() { _binDataProto.install(_global); _bsonProto.install(_global); + _codeProto.install(_global); _dbPointerProto.install(_global); _dbRefProto.install(_global); _errorProto.install(_global); diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h index de496546042..26fd75fb574 100644 --- a/src/mongo/scripting/mozjs/implscope.h +++ b/src/mongo/scripting/mozjs/implscope.h @@ -34,6 +34,7 @@ #include "mongo/client/dbclientcursor.h" #include "mongo/scripting/mozjs/bindata.h" #include "mongo/scripting/mozjs/bson.h" +#include "mongo/scripting/mozjs/code.h" #include "mongo/scripting/mozjs/countdownlatch.h" #include "mongo/scripting/mozjs/cursor.h" #include "mongo/scripting/mozjs/cursor_handle.h" @@ -106,6 +107,8 @@ public: void gc() override; + bool isJavaScriptProtectionEnabled() const; + double getNumber(const char* field) override; int getNumberInt(const char* field) override; long long getNumberLongLong(const char* field) override; @@ -262,6 +265,12 @@ public: } template <typename T> + typename std::enable_if<std::is_same<T, CodeInfo>::value, WrapType<T>&>::type + getProto() { + return _codeProto; + } + + template <typename T> typename std::enable_if<std::is_same<T, ObjectInfo>::value, WrapType<T>&>::type getProto() { return _objectProto; } @@ -369,6 +378,7 @@ private: WrapType<BinDataInfo> _binDataProto; WrapType<BSONInfo> _bsonProto; + WrapType<CodeInfo> _codeProto; WrapType<CountDownLatchInfo> _countDownLatchProto; WrapType<CursorInfo> _cursorProto; WrapType<CursorHandleInfo> _cursorHandleProto; diff --git a/src/mongo/scripting/mozjs/internedstring.defs b/src/mongo/scripting/mozjs/internedstring.defs index 46bc16b2c54..40bf0b97498 100644 --- a/src/mongo/scripting/mozjs/internedstring.defs +++ b/src/mongo/scripting/mozjs/internedstring.defs @@ -8,6 +8,7 @@ MONGO_MOZJS_INTERNED_STRING(arrayAccess, "arrayAccess") MONGO_MOZJS_INTERNED_STRING(_batchSize, "_batchSize") MONGO_MOZJS_INTERNED_STRING(bottom, "bottom") MONGO_MOZJS_INTERNED_STRING(_bson, "_bson") +MONGO_MOZJS_INTERNED_STRING(code, "code") MONGO_MOZJS_INTERNED_STRING(_collection, "_collection") MONGO_MOZJS_INTERNED_STRING(constructor, "constructor") MONGO_MOZJS_INTERNED_STRING(_cursor, "_cursor") @@ -39,6 +40,7 @@ MONGO_MOZJS_INTERNED_STRING(_options, "_options") MONGO_MOZJS_INTERNED_STRING(_query, "_query") MONGO_MOZJS_INTERNED_STRING(readOnly, "readOnly") MONGO_MOZJS_INTERNED_STRING(_ro, "_ro") +MONGO_MOZJS_INTERNED_STRING(scope, "scope") MONGO_MOZJS_INTERNED_STRING(_shortName, "_shortName") MONGO_MOZJS_INTERNED_STRING(singleton, "singleton") MONGO_MOZJS_INTERNED_STRING(_skip, "_skip") diff --git a/src/mongo/scripting/mozjs/valuereader.cpp b/src/mongo/scripting/mozjs/valuereader.cpp index c80efc017da..630d446a4d0 100644 --- a/src/mongo/scripting/mozjs/valuereader.cpp +++ b/src/mongo/scripting/mozjs/valuereader.cpp @@ -53,13 +53,35 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent switch (elem.type()) { case mongo::Code: - scope->newFunction(elem.valueStringData(), _value); + // javascriptProtection prevents Code and CodeWScope BSON types from + // being automatically marshalled into executable functions. + if (scope->isJavaScriptProtectionEnabled()) { + JS::AutoValueArray<1> args(_context); + ValueReader(_context, args[0]).fromStringData(elem.valueStringData()); + + JS::RootedObject obj(_context); + scope->getProto<CodeInfo>().newInstance(args, _value); + } + else { + scope->newFunction(elem.valueStringData(), _value); + } return; case mongo::CodeWScope: - if (!elem.codeWScopeObject().isEmpty()) - warning() << "CodeWScope doesn't transfer to db.eval"; - scope->newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1), - _value); + if (scope->isJavaScriptProtectionEnabled()) { + JS::AutoValueArray<2> args(_context); + + ValueReader(_context, args[0]).fromStringData(elem.valueStringData()); + ValueReader(_context, args[1]).fromBSON(elem.codeWScopeObject(), + nullptr, readOnly); + + scope->getProto<CodeInfo>().newInstance(args, _value); + } + else { + if (!elem.codeWScopeObject().isEmpty()) + warning() << "CodeWScope doesn't transfer to db.eval"; + scope->newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1), + _value); + } return; case mongo::Symbol: case mongo::String: diff --git a/src/mongo/scripting/mozjs/valuewriter.cpp b/src/mongo/scripting/mozjs/valuewriter.cpp index 080e8317bbf..967a2947cd8 100644 --- a/src/mongo/scripting/mozjs/valuewriter.cpp +++ b/src/mongo/scripting/mozjs/valuewriter.cpp @@ -266,6 +266,27 @@ void ValueWriter::_writeObject(BSONObjBuilder* b, return; } + if (scope->getProto<CodeInfo>().getJSClass() == jsclass) { + if (o.hasOwnField(InternedString::scope) // CodeWScope + && o.type(InternedString::scope) == mongo::Object) { + + if (o.type(InternedString::code) != mongo::String) { + uasserted(ErrorCodes::BadValue, "code must be a string"); + } + + b->appendCodeWScope(sd, o.getString(InternedString::code), + o.getObject(InternedString::scope)); + } + else { // Code + if (o.type(InternedString::code) != mongo::String) { + uasserted(ErrorCodes::BadValue, "code must be a string"); + } + + b->appendCode(sd, o.getString(InternedString::code)); + } + return; + } + if (scope->getProto<NumberDecimalInfo>().getJSClass() == jsclass) { b->append(sd, NumberDecimalInfo::ToNumberDecimal(_context, obj)); diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index e17cfa36858..91892ff55e3 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -1155,7 +1155,12 @@ DB.prototype.getQueryOptions = function() { DB.prototype.loadServerScripts = function(){ var global = Function('return this')(); this.system.js.find().forEach(function(u) { - global[u._id] = u.value; + if (u.value.constructor === Code) { + global[u._id] = eval("(" + u.value.code + ")"); + } + else { + global[u._id] = u.value; + } }); }; diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp index 22b9e22d91d..c064c292e83 100644 --- a/src/mongo/shell/dbshell.cpp +++ b/src/mongo/shell/dbshell.cpp @@ -683,6 +683,7 @@ int _main(int argc, char* argv[], char** envp) { mongo::ScriptEngine::setup(); mongo::globalScriptEngine->setScopeInitCallback(mongo::shell_utils::initScope); mongo::globalScriptEngine->enableJIT(!shellGlobalParams.nojit); + mongo::globalScriptEngine->enableJavaScriptProtection(shellGlobalParams.javascriptProtection); auto poolGuard = MakeGuard([] { ScriptEngine::dropScopeCache(); }); diff --git a/src/mongo/shell/shell_options.cpp b/src/mongo/shell/shell_options.cpp index 62ce19c12a3..5094f01276c 100644 --- a/src/mongo/shell/shell_options.cpp +++ b/src/mongo/shell/shell_options.cpp @@ -124,6 +124,11 @@ Status addMongoShellOptions(moe::OptionSection* options) { moe::Switch, "disable the Javascript Just In Time compiler"); + options->addOptionChaining("enableJavaScriptProtection", + "enableJavaScriptProtection", + moe::Switch, + "disable automatic JavaScript function marshalling"); + Status ret = Status::OK(); #ifdef MONGO_CONFIG_SSL ret = addSSLClientOptions(options); @@ -260,6 +265,9 @@ Status storeMongoShellOptions(const moe::Environment& params, if (params.count("nodb")) { shellGlobalParams.nodb = true; } + if (params.count("enableJavaScriptProtection")) { + shellGlobalParams.javascriptProtection = true; + } if (params.count("norc")) { shellGlobalParams.norc = true; } diff --git a/src/mongo/shell/shell_options.h b/src/mongo/shell/shell_options.h index 2e94ff92282..4699f3d09f4 100644 --- a/src/mongo/shell/shell_options.h +++ b/src/mongo/shell/shell_options.h @@ -62,6 +62,7 @@ struct ShellGlobalParams { bool nodb; bool norc; bool nojit = false; + bool javascriptProtection = false; std::string script; |