1 // dbeval.cpp
2 
3 
4 /**
5  *    Copyright (C) 2018-present MongoDB, Inc.
6  *
7  *    This program is free software: you can redistribute it and/or modify
8  *    it under the terms of the Server Side Public License, version 1,
9  *    as published by MongoDB, Inc.
10  *
11  *    This program is distributed in the hope that it will be useful,
12  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *    Server Side Public License for more details.
15  *
16  *    You should have received a copy of the Server Side Public License
17  *    along with this program. If not, see
18  *    <http://www.mongodb.com/licensing/server-side-public-license>.
19  *
20  *    As a special exception, the copyright holders give permission to link the
21  *    code of portions of this program with the OpenSSL library under certain
22  *    conditions as described in each individual source file and distribute
23  *    linked combinations including the program with the OpenSSL library. You
24  *    must comply with the Server Side Public License in all respects for
25  *    all of the code used other than as permitted herein. If you modify file(s)
26  *    with this exception, you may extend this exception to your version of the
27  *    file(s), but you are not obligated to do so. If you do not wish to do so,
28  *    delete this exception statement from your version. If you delete this
29  *    exception statement from all source files in the program, then also delete
30  *    it in the license file.
31  */
32 
33 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
34 
35 #include "mongo/platform/basic.h"
36 
37 #include "mongo/bson/util/builder.h"
38 #include "mongo/db/auth/authorization_manager.h"
39 #include "mongo/db/auth/authorization_manager_global.h"
40 #include "mongo/db/auth/authorization_session.h"
41 #include "mongo/db/client.h"
42 #include "mongo/db/commands.h"
43 #include "mongo/db/db_raii.h"
44 #include "mongo/db/introspect.h"
45 #include "mongo/db/jsobj.h"
46 #include "mongo/db/json.h"
47 #include "mongo/db/operation_context.h"
48 #include "mongo/db/s/operation_sharding_state.h"
49 #include "mongo/s/stale_exception.h"
50 #include "mongo/scripting/engine.h"
51 #include "mongo/util/log.h"
52 #include "mongo/util/scopeguard.h"
53 
54 namespace mongo {
55 
56 using std::unique_ptr;
57 using std::dec;
58 using std::endl;
59 using std::string;
60 using std::stringstream;
61 
62 namespace {
63 
64 const int edebug = 0;
65 
dbEval(OperationContext * opCtx,const string & dbName,const BSONObj & cmd,BSONObjBuilder & result,string & errmsg)66 bool dbEval(OperationContext* opCtx,
67             const string& dbName,
68             const BSONObj& cmd,
69             BSONObjBuilder& result,
70             string& errmsg) {
71     RARELY {
72         warning() << "the eval command is deprecated" << startupWarningsLog;
73     }
74 
75     const BSONElement e = cmd.firstElement();
76     uassert(
77         10046, "eval needs Code", e.type() == Code || e.type() == CodeWScope || e.type() == String);
78 
79     const char* code = 0;
80     switch (e.type()) {
81         case String:
82         case Code:
83             code = e.valuestr();
84             break;
85         case CodeWScope:
86             code = e.codeWScopeCode();
87             break;
88         default:
89             verify(0);
90     }
91 
92     verify(code);
93 
94     if (!getGlobalScriptEngine()) {
95         errmsg = "db side execution is disabled";
96         return false;
97     }
98 
99     unique_ptr<Scope> s(getGlobalScriptEngine()->newScope());
100     s->registerOperation(opCtx);
101 
102     ScriptingFunction f = s->createFunction(code);
103     if (f == 0) {
104         errmsg = string("compile failed: ") + s->getError();
105         return false;
106     }
107 
108     s->localConnectForDbEval(opCtx, dbName.c_str());
109 
110     if (e.type() == CodeWScope) {
111         s->init(e.codeWScopeScopeDataUnsafe());
112     }
113 
114     BSONObj args;
115     {
116         BSONElement argsElement = cmd.getField("args");
117         if (argsElement.type() == Array) {
118             args = argsElement.embeddedObject();
119             if (edebug) {
120                 log() << "args:" << args;
121                 log() << "code:\n" << redact(code);
122             }
123         }
124     }
125 
126     int res;
127     {
128         Timer t;
129         res = s->invoke(f, &args, 0, 0);
130         int m = t.millis();
131         if (m > serverGlobalParams.slowMS) {
132             log() << "dbeval slow, time: " << dec << m << "ms " << dbName;
133             if (m >= 1000)
134                 log() << redact(code);
135             else
136                 OCCASIONALLY log() << redact(code);
137         }
138     }
139 
140     if (res || s->isLastRetNativeCode()) {
141         result.append("errno", (double)res);
142         errmsg = "invoke failed: ";
143         if (s->isLastRetNativeCode())
144             errmsg += "cannot return native function";
145         else
146             errmsg += s->getError();
147 
148         return false;
149     }
150 
151     s->append(result, "retval", "__returnValue");
152 
153     return true;
154 }
155 
156 
157 class CmdEval : public ErrmsgCommandDeprecated {
158 public:
slaveOk() const159     virtual bool slaveOk() const {
160         return false;
161     }
162 
help(stringstream & help) const163     virtual void help(stringstream& help) const {
164         help << "DEPRECATED\n"
165              << "Evaluate javascript at the server.\n"
166              << "http://dochub.mongodb.org/core/serversidecodeexecution";
167     }
supportsWriteConcern(const BSONObj & cmd) const168     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
169         return false;
170     }
addRequiredPrivileges(const std::string & dbname,const BSONObj & cmdObj,std::vector<Privilege> * out)171     virtual void addRequiredPrivileges(const std::string& dbname,
172                                        const BSONObj& cmdObj,
173                                        std::vector<Privilege>* out) {
174         RoleGraph::generateUniversalPrivileges(out);
175     }
176 
CmdEval()177     CmdEval() : ErrmsgCommandDeprecated("eval", "$eval") {}
178 
errmsgRun(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,string & errmsg,BSONObjBuilder & result)179     bool errmsgRun(OperationContext* opCtx,
180                    const string& dbname,
181                    const BSONObj& cmdObj,
182                    string& errmsg,
183                    BSONObjBuilder& result) {
184         // Note: 'eval' is not allowed to touch sharded namespaces, but we can't check the
185         // shardVersions of the namespaces accessed in the script until the script is evaluated.
186         // Instead, we enforce that the script does not access sharded namespaces by ensuring the
187         // shardVersion is set to UNSHARDED on the OperationContext before sending the script to be
188         // evaluated.
189         auto& oss = OperationShardingState::get(opCtx);
190         uassert(ErrorCodes::IllegalOperation,
191                 "can't send a shardVersion with the 'eval' command, since you can't use sharded "
192                 "collections from 'eval'",
193                 !oss.hasShardVersion());
194 
195         // Set the shardVersion to UNSHARDED. The "namespace" used does not matter, because if a
196         // shardVersion is set on the OperationContext, a check for a different namespace will
197         // default to UNSHARDED.
198         oss.setShardVersion(NamespaceString(dbname), ChunkVersion::UNSHARDED());
199         const auto shardVersionGuard =
200             MakeGuard([&]() { oss.unsetShardVersion(NamespaceString(dbname)); });
201 
202         try {
203             if (cmdObj["nolock"].trueValue()) {
204                 return dbEval(opCtx, dbname, cmdObj, result, errmsg);
205             }
206 
207             Lock::GlobalWrite lk(opCtx);
208 
209             OldClientContext ctx(opCtx, dbname, false /* no shard version checking here */);
210 
211             return dbEval(opCtx, dbname, cmdObj, result, errmsg);
212         } catch (const AssertionException& ex) {
213             // Convert a stale shardVersion error to a stronger error to prevent this node or the
214             // sending node from believing it needs to refresh its routing table.
215             if (ex.code() == ErrorCodes::StaleConfig) {
216                 uasserted(ErrorCodes::BadValue,
217                           str::stream() << "can't use sharded collection from db.eval");
218             }
219             throw;
220         }
221     }
222 
223 } cmdeval;
224 
225 }  // namespace
226 }  // namespace mongo
227