Chromium Code Reviews| 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..d079f949b09217dc3f6f4403c2de953e8bb72dc5 |
| --- /dev/null |
| +++ b/sandbox/mac/launchd_interception_server.cc |
| @@ -0,0 +1,254 @@ |
| +// 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 "sandbox/mac/bootstrap_sandbox.h" |
| + |
| +namespace sandbox { |
| + |
| +LaunchdInterceptionServer::LaunchdInterceptionServer( |
| + const BootstrapSandbox* sandbox) |
| + : sandbox_(sandbox), |
| + server_port_(MACH_PORT_NULL), |
| + sandbox_port_(MACH_PORT_NULL), |
| + compat_shim_(GetLaunchdCompatibilityShim()) { |
| +} |
| + |
| +LaunchdInterceptionServer::~LaunchdInterceptionServer() { |
| + dispatch_release(server_source_); |
| + dispatch_release(server_queue_); |
| +} |
| + |
| +bool LaunchdInterceptionServer::Initialize() { |
| + mach_port_t task = mach_task_self(); |
| + |
| + mach_port_t port; |
| + if (mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port) != |
| + KERN_SUCCESS) { |
| + return false; |
| + } |
| + if (mach_port_insert_right(task, port, port, MACH_MSG_TYPE_MAKE_SEND) != |
| + KERN_SUCCESS) { |
| + return false; |
| + } |
| + server_port_.reset(port); |
| + |
| + server_queue_ = dispatch_queue_create( |
| + "chromium.sandbox.LaunchdInterceptionServer", NULL); |
|
Mark Mentovai
2014/05/06 20:51:50
org.chromium.blah?
Mark Mentovai
2014/05/06 20:51:50
Hopefully nothing on this queue will result in a c
Robert Sesek
2014/05/08 20:58:12
Oops lost org. somehow.
Robert Sesek
2014/05/08 20:58:12
It shouldn't…
|
| + 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_); |
| + |
| + if (mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port) != |
|
Mark Mentovai
2014/05/06 20:51:50
Can we get away with not even having a receive rig
Robert Sesek
2014/05/08 20:58:12
Nope, can't do that. xnu-2422.1.72/osfmk/ipc/mach_
|
| + KERN_SUCCESS) { |
| + return false; |
| + } |
| + sandbox_port_.reset(port); |
| + |
| + return true; |
| +} |
| + |
| +void LaunchdInterceptionServer::ReceiveMessage() { |
| + // The buffer size 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; |
|
Mark Mentovai
2014/05/06 20:51:50
Does this include space for the trailer?
Robert Sesek
2014/05/08 20:58:12
Done.
|
| + |
| + 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_port_t task = mach_task_self(); |
| + kern_return_t kr; |
|
Mark Mentovai
2014/05/06 20:51:50
Don’t declare this ’til you use it.
Robert Sesek
2014/05/08 20:58:12
Done.
|
| + |
| + // 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. |
| + mach_msg_header_t *request, *reply; |
|
Mark Mentovai
2014/05/06 20:51:50
Don’t declare *reply ’til you use it. (And Chromiu
Robert Sesek
2014/05/08 20:58:12
Done.
|
| + |
| + kr = vm_allocate(task, reinterpret_cast<vm_address_t*>(&request), kBufferSize, |
|
Mark Mentovai
2014/05/06 20:51:50
If you’re vm_allocating, you might as well round k
Robert Sesek
2014/05/08 20:58:12
Done.
|
| + VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE); |
|
Mark Mentovai
2014/05/06 20:51:50
Spaces around |. Same on line 85.
Robert Sesek
2014/05/08 20:58:12
Done.
|
| + if (kr != KERN_SUCCESS) { |
| + LOG(ERROR) << "Failed to allocate request buffer. Error #" << kr << ": " |
| + << mach_error_string(kr); |
| + return; |
| + } |
| + |
| + kr = vm_allocate(task, reinterpret_cast<vm_address_t*>(&reply), kBufferSize, |
| + VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE); |
| + if (kr != KERN_SUCCESS) { |
| + LOG(ERROR) << "Failed to allocate reply buffer. Error #" << kr << ": " |
|
Mark Mentovai
2014/05/06 20:51:50
Leaks the request buffer.
Robert Sesek
2014/05/08 20:58:12
Done.
|
| + << mach_error_string(kr); |
| + return; |
| + } |
| + |
| + kr = mach_msg(request, kRcvOptions, 0, kBufferSize, server_port_.get(), |
| + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| + if (kr != KERN_SUCCESS) { |
| + LOG(ERROR) << "Unable to receive message. Error #" << kr << ": " |
|
Mark Mentovai
2014/05/06 20:51:50
Leaks the request and reply buffers. Do you want b
Robert Sesek
2014/05/08 20:58:12
Please. Want to upstream it?
|
| + << mach_error_string(kr); |
| + 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); |
| + |
| + // Checking return codes here is not useful, so don't. |
| + vm_deallocate(task, reinterpret_cast<vm_address_t>(request), kBufferSize); |
|
Mark Mentovai
2014/05/06 20:51:50
The nice thing about writing your own server loop
Robert Sesek
2014/05/08 20:58:12
I moved the buffers to be member variables, since
|
| + vm_deallocate(task, reinterpret_cast<vm_address_t>(reply), kBufferSize); |
| +} |
| + |
| +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 proceess or an unsandboxed child. |
|
Mark Mentovai
2014/05/06 20:51:50
Do we have unsandboxed children? Is it fair to req
Robert Sesek
2014/05/08 20:58:12
We do have unsandboxed children. Besides certain p
|
| + 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) { |
| + // The vproc system in launchd is needed to fully check-in processes with |
| + // launchd. These messages are generally safe and merely permit swapping |
| + // small bits of data across processes. |
| + VLOG(2) << "Forwarding vproc swap message #" << request->msgh_id; |
|
Mark Mentovai
2014/05/06 20:51:50
I’m still afraid of these. If they’re required for
Robert Sesek
2014/05/08 20:58:12
I'm pretty confident these are safe. Because launc
|
| + ForwardMessage(request, reply); |
| + } 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)); |
|
Mark Mentovai
2014/05/06 20:51:50
Can we make this interface use StringBuffer or som
Robert Sesek
2014/05/08 20:58:12
I'm assuming you meant StringPiece. But no because
|
| + |
| + 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_SUBSTITUE_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) { |
| + LOG(ERROR) << "Unable to insert right on result_port. Error #" << kr |
| + << ": " << mach_error_string(kr); |
| + } |
| + |
| + compat_shim_.look_up2_fill_reply(reply, result_port); |
| + SendReply(reply); |
| + } else { |
| + NOTREACHED(); |
|
Mark Mentovai
2014/05/06 20:51:50
Send a reject message or the child will hang forev
Robert Sesek
2014/05/08 20:58:12
I think NOTREACHED() means NOTREACHED().
|
| + } |
| +} |
| + |
| +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) { |
| + LOG(ERROR) << "Unable to send intercepted reply message. Error #" |
| + << kr << ": " << mach_error_string(kr); |
|
Mark Mentovai
2014/05/06 20:51:50
All of these…I should really check in my mach_logg
Robert Sesek
2014/05/08 20:58:12
Yes, you should :). Would review for free.
|
| + } |
| +} |
| + |
| +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) { |
| + LOG(ERROR) << "Unable to forward message to the real launchd. Error #" |
| + << kr << ": " << mach_error_string(kr); |
| + } |
| +} |
| + |
| +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 |