| Index: sandbox/mac/launchd_interception_server.cc
|
| diff --git a/sandbox/mac/launchd_interception_server.cc b/sandbox/mac/launchd_interception_server.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..73e0d6720b862cdfffb0cd8028dc410e81fd5977
|
| --- /dev/null
|
| +++ b/sandbox/mac/launchd_interception_server.cc
|
| @@ -0,0 +1,273 @@
|
| +// 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 <bsm/libbsm.h>
|
| +#include <servers/bootstrap.h>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/mac/mach_logging.h"
|
| +#include "sandbox/mac/bootstrap_sandbox.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 = mach_vm_round_page(2096 +
|
| + sizeof(mach_msg_audit_trailer_t));
|
| +
|
| +LaunchdInterceptionServer::LaunchdInterceptionServer(
|
| + const BootstrapSandbox* sandbox)
|
| + : sandbox_(sandbox),
|
| + server_port_(MACH_PORT_NULL),
|
| + server_queue_(NULL),
|
| + server_source_(NULL),
|
| + sandbox_port_(MACH_PORT_NULL),
|
| + compat_shim_(GetLaunchdCompatibilityShim()) {
|
| +}
|
| +
|
| +LaunchdInterceptionServer::~LaunchdInterceptionServer() {
|
| + if (server_source_)
|
| + dispatch_release(server_source_);
|
| + if (server_queue_)
|
| + dispatch_release(server_queue_);
|
| +}
|
| +
|
| +bool LaunchdInterceptionServer::Initialize() {
|
| + mach_port_t task = mach_task_self();
|
| + kern_return_t kr;
|
| +
|
| + // Allocate a port for use as a new bootstrap 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 new bootstrap port.";
|
| + return false;
|
| + }
|
| + if ((kr = mach_port_insert_right(task, port, port, MACH_MSG_TYPE_MAKE_SEND))
|
| + != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Failed to insert send right on bootstrap port.";
|
| + return false;
|
| + }
|
| + server_port_.reset(port);
|
| +
|
| + // Allocate the message request and reply buffers.
|
| + const int kMachMsgMemoryFlags = VM_MAKE_TAG(VM_MEMORY_MACH_MSG) |
|
| + VM_FLAGS_ANYWHERE;
|
| + vm_address_t buffer = 0;
|
| +
|
| + kr = vm_allocate(task, &buffer, kBufferSize, kMachMsgMemoryFlags);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Failed to allocate request buffer.";
|
| + return false;
|
| + }
|
| + request_buffer_.reset(buffer, kBufferSize);
|
| +
|
| + kr = vm_allocate(task, &buffer, kBufferSize, kMachMsgMemoryFlags);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Failed to allocate reply buffer.";
|
| + return false;
|
| + }
|
| + reply_buffer_.reset(buffer, kBufferSize);
|
| +
|
| + // Allocate the dummy sandbox 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);
|
| +
|
| + // Set up the dispatch queue to service the bootstrap port.
|
| + // TODO(rsesek): Specify DISPATCH_QUEUE_SERIAL, in the 10.7 SDK. NULL means
|
| + // the same thing but is not symbolically clear.
|
| + server_queue_ = dispatch_queue_create(
|
| + "org.chromium.sandbox.LaunchdInterceptionServer", NULL);
|
| + server_source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV,
|
| + server_port_.get(), 0, server_queue_);
|
| + dispatch_source_set_event_handler(server_source_, ^{ ReceiveMessage(); });
|
| + dispatch_resume(server_source_);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::ReceiveMessage() {
|
| + const mach_msg_options_t kRcvOptions = MACH_RCV_MSG |
|
| + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
|
| + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
|
| +
|
| + mach_msg_header_t* request =
|
| + reinterpret_cast<mach_msg_header_t*>(request_buffer_.address());
|
| + mach_msg_header_t* reply =
|
| + reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address());
|
| +
|
| + // Zero out the buffers from handling any previous message.
|
| + bzero(request, kBufferSize);
|
| + bzero(reply, kBufferSize);
|
| +
|
| + // A Mach message server-once. The system library to run a message server
|
| + // cannot be used here, because some requests are conditionally forwarded
|
| + // to another server.
|
| + kern_return_t kr = mach_msg(request, kRcvOptions, 0, kBufferSize,
|
| + server_port_.get(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Unable to receive message.";
|
| + return;
|
| + }
|
| +
|
| + // Set up a reply message in case it will be used.
|
| + reply->msgh_bits = MACH_MSGH_BITS_REMOTE(reply->msgh_bits);
|
| + // Since mach_msg will automatically swap the request and reply ports,
|
| + // undo that.
|
| + reply->msgh_remote_port = request->msgh_remote_port;
|
| + reply->msgh_local_port = MACH_PORT_NULL;
|
| + // MIG servers simply add 100 to the request ID to generate the reply ID.
|
| + reply->msgh_id = request->msgh_id + 100;
|
| +
|
| + // Process the message.
|
| + DemuxMessage(request, reply);
|
| +
|
| + // Free any descriptors in the message body.
|
| + mach_msg_destroy(request);
|
| + mach_msg_destroy(reply);
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::DemuxMessage(mach_msg_header_t* request,
|
| + mach_msg_header_t* reply) {
|
| + VLOG(3) << "Incoming message #" << request->msgh_id;
|
| +
|
| + // Get the PID of the task that sent this request. This requires getting at
|
| + // the trailer of the message, from the header.
|
| + mach_msg_audit_trailer_t* trailer =
|
| + reinterpret_cast<mach_msg_audit_trailer_t*>(
|
| + reinterpret_cast<vm_address_t>(request) +
|
| + round_msg(request->msgh_size));
|
| + // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid().
|
| + pid_t sender_pid;
|
| + audit_token_to_au32(trailer->msgh_audit,
|
| + NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL);
|
| +
|
| + if (sandbox_->PolicyForProcess(sender_pid) == NULL) {
|
| + // No sandbox policy is in place for the sender of this message, which
|
| + // means it is from the sandbox host process or an unsandboxed child.
|
| + VLOG(3) << "Message from pid " << sender_pid << " forwarded to launchd";
|
| + ForwardMessage(request, reply);
|
| + return;
|
| + }
|
| +
|
| + if (request->msgh_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, reply, sender_pid);
|
| + } else if (request->msgh_id == compat_shim_.msg_id_swap_integer) {
|
| + // Ensure that any vproc_swap_integer requests are safe.
|
| + HandleSwapInteger(request, reply, sender_pid);
|
| + } else {
|
| + // All other messages are not permitted.
|
| + VLOG(1) << "Rejecting unhandled message #" << request->msgh_id;
|
| + RejectMessage(request, reply, MIG_REMOTE_ERROR);
|
| + }
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::HandleLookUp(mach_msg_header_t* request,
|
| + mach_msg_header_t* reply,
|
| + pid_t sender_pid) {
|
| + 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 one is not found, use
|
| + // a safe default, POLICY_DENY_ERROR.
|
| + const BootstrapSandboxPolicy* policy = sandbox_->PolicyForProcess(sender_pid);
|
| + const BootstrapSandboxPolicy::const_iterator it =
|
| + policy->find(request_service_name);
|
| + Rule rule(POLICY_DENY_ERROR);
|
| + if (it != policy->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, reply);
|
| + } 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;
|
| + RejectMessage(request, reply, 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;
|
| +
|
| + // Grant an additional send right on the result_port so that it can be
|
| + // sent to the sandboxed child process.
|
| + kern_return_t kr = mach_port_insert_right(mach_task_self(),
|
| + result_port, result_port, MACH_MSG_TYPE_MAKE_SEND);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Unable to insert right on result_port.";
|
| + }
|
| +
|
| + compat_shim_.look_up2_fill_reply(reply, result_port);
|
| + SendReply(reply);
|
| + } else {
|
| + NOTREACHED();
|
| + }
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::HandleSwapInteger(mach_msg_header_t* request,
|
| + mach_msg_header_t* reply,
|
| + pid_t sender_pid) {
|
| + // TODO(rsesek): Crack the message and ensure that the swap is only being
|
| + // used to get the value of a VPROC key, and do not allow setting it.
|
| + VLOG(2) << "Forwarding vproc swap message #" << request->msgh_id;
|
| + ForwardMessage(request, reply);
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::SendReply(mach_msg_header_t* reply) {
|
| + kern_return_t kr = mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0,
|
| + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Unable to send intercepted reply message.";
|
| + }
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::ForwardMessage(mach_msg_header_t* request,
|
| + mach_msg_header_t* reply) {
|
| + request->msgh_local_port = request->msgh_remote_port;
|
| + request->msgh_remote_port = sandbox_->real_bootstrap_port();
|
| + // Preserve the msgh_bits that do not deal with the local and remote ports.
|
| + request->msgh_bits = (request->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) |
|
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE);
|
| + kern_return_t kr = mach_msg_send(request);
|
| + if (kr != KERN_SUCCESS) {
|
| + MACH_LOG(ERROR, kr) << "Unable to forward message to the real launchd.";
|
| + }
|
| +}
|
| +
|
| +void LaunchdInterceptionServer::RejectMessage(mach_msg_header_t* request,
|
| + mach_msg_header_t* reply,
|
| + int error_code) {
|
| + mig_reply_error_t* error_reply = reinterpret_cast<mig_reply_error_t*>(reply);
|
| + error_reply->Head.msgh_size = sizeof(mig_reply_error_t);
|
| + error_reply->Head.msgh_bits =
|
| + MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE);
|
| + error_reply->NDR = NDR_record;
|
| + error_reply->RetCode = error_code;
|
| + SendReply(&error_reply->Head);
|
| +}
|
| +
|
| +} // namespace sandbox
|
|
|