// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sandbox/mac/launchd_interception_server.h" #include #include "base/logging.h" #include "base/mac/mach_logging.h" #include "sandbox/mac/bootstrap_sandbox.h" #include "sandbox/mac/mach_message_server.h" namespace sandbox { // The buffer size for all launchd messages. This comes from // sizeof(union __RequestUnion__vproc_mig_job_subsystem) in launchd, and it // is larger than the __ReplyUnion. const mach_msg_size_t kBufferSize = 2096; LaunchdInterceptionServer::LaunchdInterceptionServer( const BootstrapSandbox* sandbox) : sandbox_(sandbox), sandbox_port_(MACH_PORT_NULL), compat_shim_(GetLaunchdCompatibilityShim()) { } LaunchdInterceptionServer::~LaunchdInterceptionServer() { } bool LaunchdInterceptionServer::Initialize(mach_port_t server_receive_right) { mach_port_t task = mach_task_self(); kern_return_t kr; // Allocate the dummy sandbox port. mach_port_t port; if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != KERN_SUCCESS) { MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port."; return false; } sandbox_port_.reset(port); if ((kr = mach_port_insert_right(task, sandbox_port_, sandbox_port_, MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS)) { MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port send right."; return false; } sandbox_send_port_.reset(sandbox_port_); message_server_.reset( new MachMessageServer(this, server_receive_right, kBufferSize)); return message_server_->Initialize(); } void LaunchdInterceptionServer::DemuxMessage(IPCMessage request) { const uint64_t message_id = compat_shim_.ipc_message_get_id(request); VLOG(3) << "Incoming message #" << message_id; pid_t sender_pid = message_server_->GetMessageSenderPID(request); const BootstrapSandboxPolicy* policy = sandbox_->PolicyForProcess(sender_pid); if (policy == NULL) { // No sandbox policy is in place for the sender of this message, which // means it came from the unknown. Reject it. VLOG(3) << "Message from unknown pid " << sender_pid << " rejected."; message_server_->RejectMessage(request, MIG_REMOTE_ERROR); return; } if (message_id == compat_shim_.msg_id_look_up2) { // Filter messages sent via bootstrap_look_up to enforce the sandbox policy // over the bootstrap namespace. HandleLookUp(request, policy); } else if (message_id == compat_shim_.msg_id_swap_integer) { // Ensure that any vproc_swap_integer requests are safe. HandleSwapInteger(request); } else { // All other messages are not permitted. VLOG(1) << "Rejecting unhandled message #" << message_id; message_server_->RejectMessage(request, MIG_REMOTE_ERROR); } } void LaunchdInterceptionServer::HandleLookUp( IPCMessage request, const BootstrapSandboxPolicy* policy) { const std::string request_service_name( compat_shim_.look_up2_get_request_name(request)); VLOG(2) << "Incoming look_up2 request for " << request_service_name; // Find the Rule for this service. If a named rule is not found, use the // default specified by the policy. const BootstrapSandboxPolicy::NamedRules::const_iterator it = policy->rules.find(request_service_name); Rule rule(policy->default_rule); if (it != policy->rules.end()) rule = it->second; if (rule.result == POLICY_ALLOW) { // This service is explicitly allowed, so this message will not be // intercepted by the sandbox. VLOG(1) << "Permitting and forwarding look_up2: " << request_service_name; ForwardMessage(request); } else if (rule.result == POLICY_DENY_ERROR) { // The child is not permitted to look up this service. Send a MIG error // reply to the client. Returning a NULL or unserviced port for a look up // can cause clients to crash or hang. VLOG(1) << "Denying look_up2 with MIG error: " << request_service_name; message_server_->RejectMessage(request, BOOTSTRAP_UNKNOWN_SERVICE); } else if (rule.result == POLICY_DENY_DUMMY_PORT || rule.result == POLICY_SUBSTITUTE_PORT) { // The policy result is to deny access to the real service port, replying // with a sandboxed port in its stead. Use either the dummy sandbox_port_ // or the one specified in the policy. VLOG(1) << "Intercepting look_up2 with a sandboxed service port: " << request_service_name; mach_port_t result_port; if (rule.result == POLICY_DENY_DUMMY_PORT) result_port = sandbox_port_.get(); else result_port = rule.substitute_port; IPCMessage reply = message_server_->CreateReply(request); compat_shim_.look_up2_fill_reply(reply, result_port); // If the message was sent successfully, clear the result_port out of the // message so that it is not destroyed at the end of ReceiveMessage. The // above-inserted right has been moved out of the process, and destroying // the message will unref yet another right. if (message_server_->SendReply(reply)) compat_shim_.look_up2_fill_reply(reply, MACH_PORT_NULL); } else { NOTREACHED(); } } void LaunchdInterceptionServer::HandleSwapInteger(IPCMessage request) { // Only allow getting information out of launchd. Do not allow setting // values. Two commonly observed values that are retrieved are // VPROC_GSK_MGR_PID and VPROC_GSK_TRANSACTIONS_ENABLED. if (compat_shim_.swap_integer_is_get_only(request)) { VLOG(2) << "Forwarding vproc swap_integer message."; ForwardMessage(request); } else { VLOG(2) << "Rejecting non-read-only swap_integer message."; message_server_->RejectMessage(request, BOOTSTRAP_NOT_PRIVILEGED); } } void LaunchdInterceptionServer::ForwardMessage(IPCMessage request) { message_server_->ForwardMessage(request, sandbox_->real_bootstrap_port()); } } // namespace sandbox