/** * Copyright (C) 2018 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 . * * 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/db/commands/kill_op_cmd_base.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/db/audit.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/client.h" #include "mongo/db/operation_context.h" #include "mongo/util/mongoutils/str.h" namespace mongo { Status KillOpCmdBase::checkAuthForCommand(Client* client, const std::string& dbname, const BSONObj& cmdObj) const { AuthorizationSession* authzSession = AuthorizationSession::get(client); if (authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::killop)) { // If we have administrative permission to run killop, we don't need to traverse the // Client list to figure out if we own the operation which will be terminated. return Status::OK(); } if (authzSession->isAuthenticated() && isKillingLocalOp(cmdObj.getField("op"))) { // Look up the OperationContext and see if we have permission to kill it. This is done once // here and again in the command body. The check here in the checkAuthForCommand() function // is necessary because if the check fails, it will be picked up by the auditing system. long long opId = parseOpId(cmdObj); auto lkAndOp = KillOpCmdBase::findOpForKilling(client, opId); if (lkAndOp) { // We were able to find the Operation, and we were authorized to interact with it. return Status::OK(); } } return Status(ErrorCodes::Unauthorized, "Unauthorized"); } bool KillOpCmdBase::isKillingLocalOp(const BSONElement& opElem) { return opElem.isNumber(); } boost::optional, OperationContext*>> KillOpCmdBase::findOperationContext(ServiceContext* serviceContext, unsigned int opId) { for (ServiceContext::LockedClientsCursor cursor(serviceContext); Client* opClient = cursor.next();) { stdx::unique_lock lk(*opClient); OperationContext* opCtx = opClient->getOperationContext(); if (opCtx && opCtx->getOpID() == opId) { return {std::make_tuple(std::move(lk), opCtx)}; } } return boost::none; } boost::optional, OperationContext*>> KillOpCmdBase::findOpForKilling(Client* client, unsigned int opId) { AuthorizationSession* authzSession = AuthorizationSession::get(client); auto lockAndOpCtx = findOperationContext(client->getServiceContext(), opId); if (lockAndOpCtx) { OperationContext* opToKill = std::get<1>(*lockAndOpCtx); if (authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::killop) || authzSession->isCoauthorizedWithClient(opToKill->getClient())) { return lockAndOpCtx; } } return boost::none; } void KillOpCmdBase::killLocalOperation(OperationContext* opCtx, unsigned int opToKill) { stdx::unique_lock lk; OperationContext* opCtxToKill; auto lockAndOpCtx = findOpForKilling(opCtx->getClient(), opToKill); if (!lockAndOpCtx) { // killOp always reports success past the auth check. return; } std::tie(lk, opCtxToKill) = std::move(*lockAndOpCtx); invariant(lk); opCtx->getServiceContext()->killOperation(opCtxToKill); } unsigned int KillOpCmdBase::parseOpId(const BSONObj& cmdObj) { long long op; uassertStatusOK(bsonExtractIntegerField(cmdObj, "op", &op)); uassert(26823, str::stream() << "invalid op : " << op << ". Op ID cannot be represented with 32 bits", (op >= std::numeric_limits::min()) && (op <= std::numeric_limits::max())); return static_cast(op); } } // namespace mongo