1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
32 
33 #include "mongo/platform/basic.h"
34 
35 #include <limits>
36 
37 #include "mongo/base/init.h"
38 #include "mongo/base/status.h"
39 #include "mongo/bson/util/bson_extract.h"
40 #include "mongo/db/audit.h"
41 #include "mongo/db/auth/authorization_session.h"
42 #include "mongo/db/client.h"
43 #include "mongo/db/commands.h"
44 #include "mongo/db/operation_context.h"
45 #include "mongo/db/service_context.h"
46 #include "mongo/util/log.h"
47 #include "mongo/util/mongoutils/str.h"
48 
49 namespace mongo {
50 
51 class KillOpCommand : public BasicCommand {
52 public:
KillOpCommand()53     KillOpCommand() : BasicCommand("killOp") {}
54 
55 
supportsWriteConcern(const BSONObj & cmd) const56     virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
57         return false;
58     }
59 
slaveOk() const60     bool slaveOk() const final {
61         return true;
62     }
63 
adminOnly() const64     bool adminOnly() const final {
65         return true;
66     }
67 
parseOpId(const BSONObj & cmdObj)68     static long long parseOpId(const BSONObj& cmdObj) {
69         long long op;
70         uassertStatusOK(bsonExtractIntegerField(cmdObj, "op", &op));
71 
72         // Internally opid is an unsigned 32-bit int, but as BSON only has signed integer types,
73         // we wrap values exceeding 2,147,483,647 to negative numbers. The following undoes this
74         // transformation, so users can use killOp on the (negative) opid they received.
75         if (op >= std::numeric_limits<int>::min() && op < 0)
76             op += 1ull << 32;
77 
78         uassert(26823,
79                 str::stream() << "invalid op : " << op,
80                 (op >= 0) && (op <= std::numeric_limits<unsigned int>::max()));
81 
82 
83         return op;
84     }
85 
_findOp(Client * client,unsigned int opId)86     static StatusWith<std::tuple<stdx::unique_lock<Client>, OperationContext*>> _findOp(
87         Client* client, unsigned int opId) {
88         AuthorizationSession* authzSession = AuthorizationSession::get(client);
89 
90         for (ServiceContext::LockedClientsCursor cursor(client->getServiceContext());
91              Client* opClient = cursor.next();) {
92             stdx::unique_lock<Client> lk(*opClient);
93 
94             OperationContext* opCtx = opClient->getOperationContext();
95             if (opCtx && opCtx->getOpID() == opId) {
96                 if (authzSession->isAuthorizedForActionsOnResource(
97                         ResourcePattern::forClusterResource(), ActionType::killop) ||
98                     authzSession->isCoauthorizedWithClient(opClient, lk)) {
99                     return {std::make_tuple(std::move(lk), opCtx)};
100                 }
101                 break;
102             }
103         }
104 
105         return Status(ErrorCodes::NoSuchKey, str::stream() << "Could not access opID: " << opId);
106     }
107 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)108     Status checkAuthForCommand(Client* client,
109                                const std::string& dbname,
110                                const BSONObj& cmdObj) final {
111         AuthorizationSession* authzSession = AuthorizationSession::get(client);
112 
113         if (authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
114                                                            ActionType::killop)) {
115             // If we have administrative permission to run killop, we don't need to traverse the
116             // Client list to figure out if we own the operation which will be terminated.
117             return Status::OK();
118         }
119 
120         bool isAuthenticated =
121             AuthorizationSession::get(client)->getAuthenticatedUserNames().more();
122         if (isAuthenticated) {
123             long long opId = parseOpId(cmdObj);
124             auto swLkAndOp = _findOp(client, opId);
125             if (swLkAndOp.isOK()) {
126                 // We were able to find the Operation, and we were authorized to interact with it.
127                 return Status::OK();
128             }
129         }
130         return Status(ErrorCodes::Unauthorized, "Unauthorized");
131     }
132 
run(OperationContext * opCtx,const std::string & db,const BSONObj & cmdObj,BSONObjBuilder & result)133     bool run(OperationContext* opCtx,
134              const std::string& db,
135              const BSONObj& cmdObj,
136              BSONObjBuilder& result) final {
137         long long opId = parseOpId(cmdObj);
138 
139         log() << "going to kill op: " << opId;
140         result.append("info", "attempting to kill op");
141         auto swLkAndOp = _findOp(opCtx->getClient(), opId);
142         if (swLkAndOp.isOK()) {
143             stdx::unique_lock<Client> lk;
144             OperationContext* opCtxToKill;
145             std::tie(lk, opCtxToKill) = std::move(swLkAndOp.getValue());
146             opCtx->getServiceContext()->killOperation(opCtxToKill);
147         }
148 
149         return true;
150     }
151 } killOpCmd;
152 
153 }  // namespace mongo
154