OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "sandbox/mac/launchd_interception_server.h" |
| 6 |
| 7 #include <bsm/libbsm.h> |
| 8 #include <servers/bootstrap.h> |
| 9 |
| 10 #include "base/logging.h" |
| 11 #include "base/mac/mach_logging.h" |
| 12 #include "sandbox/mac/bootstrap_sandbox.h" |
| 13 |
| 14 namespace sandbox { |
| 15 |
| 16 // The buffer size for all launchd messages. This comes from |
| 17 // sizeof(union __RequestUnion__vproc_mig_job_subsystem) in launchd, and it |
| 18 // is larger than the __ReplyUnion. |
| 19 const mach_msg_size_t kBufferSize = mach_vm_round_page(2096 + |
| 20 sizeof(mach_msg_audit_trailer_t)); |
| 21 |
| 22 LaunchdInterceptionServer::LaunchdInterceptionServer( |
| 23 const BootstrapSandbox* sandbox) |
| 24 : sandbox_(sandbox), |
| 25 server_port_(MACH_PORT_NULL), |
| 26 server_queue_(NULL), |
| 27 server_source_(NULL), |
| 28 sandbox_port_(MACH_PORT_NULL), |
| 29 compat_shim_(GetLaunchdCompatibilityShim()) { |
| 30 } |
| 31 |
| 32 LaunchdInterceptionServer::~LaunchdInterceptionServer() { |
| 33 if (server_source_) |
| 34 dispatch_release(server_source_); |
| 35 if (server_queue_) |
| 36 dispatch_release(server_queue_); |
| 37 } |
| 38 |
| 39 bool LaunchdInterceptionServer::Initialize() { |
| 40 mach_port_t task = mach_task_self(); |
| 41 kern_return_t kr; |
| 42 |
| 43 // Allocate a port for use as a new bootstrap port. |
| 44 mach_port_t port; |
| 45 if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != |
| 46 KERN_SUCCESS) { |
| 47 MACH_LOG(ERROR, kr) << "Failed to allocate new bootstrap port."; |
| 48 return false; |
| 49 } |
| 50 if ((kr = mach_port_insert_right(task, port, port, MACH_MSG_TYPE_MAKE_SEND)) |
| 51 != KERN_SUCCESS) { |
| 52 MACH_LOG(ERROR, kr) << "Failed to insert send right on bootstrap port."; |
| 53 return false; |
| 54 } |
| 55 server_port_.reset(port); |
| 56 |
| 57 // Allocate the message request and reply buffers. |
| 58 const int kMachMsgMemoryFlags = VM_MAKE_TAG(VM_MEMORY_MACH_MSG) | |
| 59 VM_FLAGS_ANYWHERE; |
| 60 vm_address_t buffer = 0; |
| 61 |
| 62 kr = vm_allocate(task, &buffer, kBufferSize, kMachMsgMemoryFlags); |
| 63 if (kr != KERN_SUCCESS) { |
| 64 MACH_LOG(ERROR, kr) << "Failed to allocate request buffer."; |
| 65 return false; |
| 66 } |
| 67 request_buffer_.reset(buffer, kBufferSize); |
| 68 |
| 69 kr = vm_allocate(task, &buffer, kBufferSize, kMachMsgMemoryFlags); |
| 70 if (kr != KERN_SUCCESS) { |
| 71 MACH_LOG(ERROR, kr) << "Failed to allocate reply buffer."; |
| 72 return false; |
| 73 } |
| 74 reply_buffer_.reset(buffer, kBufferSize); |
| 75 |
| 76 // Allocate the dummy sandbox port. |
| 77 if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != |
| 78 KERN_SUCCESS) { |
| 79 MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port."; |
| 80 return false; |
| 81 } |
| 82 sandbox_port_.reset(port); |
| 83 |
| 84 // Set up the dispatch queue to service the bootstrap port. |
| 85 // TODO(rsesek): Specify DISPATCH_QUEUE_SERIAL, in the 10.7 SDK. NULL means |
| 86 // the same thing but is not symbolically clear. |
| 87 server_queue_ = dispatch_queue_create( |
| 88 "org.chromium.sandbox.LaunchdInterceptionServer", NULL); |
| 89 server_source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, |
| 90 server_port_.get(), 0, server_queue_); |
| 91 dispatch_source_set_event_handler(server_source_, ^{ ReceiveMessage(); }); |
| 92 dispatch_resume(server_source_); |
| 93 |
| 94 return true; |
| 95 } |
| 96 |
| 97 void LaunchdInterceptionServer::ReceiveMessage() { |
| 98 const mach_msg_options_t kRcvOptions = MACH_RCV_MSG | |
| 99 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | |
| 100 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); |
| 101 |
| 102 mach_msg_header_t* request = |
| 103 reinterpret_cast<mach_msg_header_t*>(request_buffer_.address()); |
| 104 mach_msg_header_t* reply = |
| 105 reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address()); |
| 106 |
| 107 // Zero out the buffers from handling any previous message. |
| 108 bzero(request, kBufferSize); |
| 109 bzero(reply, kBufferSize); |
| 110 |
| 111 // A Mach message server-once. The system library to run a message server |
| 112 // cannot be used here, because some requests are conditionally forwarded |
| 113 // to another server. |
| 114 kern_return_t kr = mach_msg(request, kRcvOptions, 0, kBufferSize, |
| 115 server_port_.get(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| 116 if (kr != KERN_SUCCESS) { |
| 117 MACH_LOG(ERROR, kr) << "Unable to receive message."; |
| 118 return; |
| 119 } |
| 120 |
| 121 // Set up a reply message in case it will be used. |
| 122 reply->msgh_bits = MACH_MSGH_BITS_REMOTE(reply->msgh_bits); |
| 123 // Since mach_msg will automatically swap the request and reply ports, |
| 124 // undo that. |
| 125 reply->msgh_remote_port = request->msgh_remote_port; |
| 126 reply->msgh_local_port = MACH_PORT_NULL; |
| 127 // MIG servers simply add 100 to the request ID to generate the reply ID. |
| 128 reply->msgh_id = request->msgh_id + 100; |
| 129 |
| 130 // Process the message. |
| 131 DemuxMessage(request, reply); |
| 132 |
| 133 // Free any descriptors in the message body. |
| 134 mach_msg_destroy(request); |
| 135 mach_msg_destroy(reply); |
| 136 } |
| 137 |
| 138 void LaunchdInterceptionServer::DemuxMessage(mach_msg_header_t* request, |
| 139 mach_msg_header_t* reply) { |
| 140 VLOG(3) << "Incoming message #" << request->msgh_id; |
| 141 |
| 142 // Get the PID of the task that sent this request. This requires getting at |
| 143 // the trailer of the message, from the header. |
| 144 mach_msg_audit_trailer_t* trailer = |
| 145 reinterpret_cast<mach_msg_audit_trailer_t*>( |
| 146 reinterpret_cast<vm_address_t>(request) + |
| 147 round_msg(request->msgh_size)); |
| 148 // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). |
| 149 pid_t sender_pid; |
| 150 audit_token_to_au32(trailer->msgh_audit, |
| 151 NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL); |
| 152 |
| 153 if (sandbox_->PolicyForProcess(sender_pid) == NULL) { |
| 154 // No sandbox policy is in place for the sender of this message, which |
| 155 // means it is from the sandbox host process or an unsandboxed child. |
| 156 VLOG(3) << "Message from pid " << sender_pid << " forwarded to launchd"; |
| 157 ForwardMessage(request, reply); |
| 158 return; |
| 159 } |
| 160 |
| 161 if (request->msgh_id == compat_shim_.msg_id_look_up2) { |
| 162 // Filter messages sent via bootstrap_look_up to enforce the sandbox policy |
| 163 // over the bootstrap namespace. |
| 164 HandleLookUp(request, reply, sender_pid); |
| 165 } else if (request->msgh_id == compat_shim_.msg_id_swap_integer) { |
| 166 // Ensure that any vproc_swap_integer requests are safe. |
| 167 HandleSwapInteger(request, reply, sender_pid); |
| 168 } else { |
| 169 // All other messages are not permitted. |
| 170 VLOG(1) << "Rejecting unhandled message #" << request->msgh_id; |
| 171 RejectMessage(request, reply, MIG_REMOTE_ERROR); |
| 172 } |
| 173 } |
| 174 |
| 175 void LaunchdInterceptionServer::HandleLookUp(mach_msg_header_t* request, |
| 176 mach_msg_header_t* reply, |
| 177 pid_t sender_pid) { |
| 178 const std::string request_service_name( |
| 179 compat_shim_.look_up2_get_request_name(request)); |
| 180 VLOG(2) << "Incoming look_up2 request for " << request_service_name; |
| 181 |
| 182 // Find the Rule for this service. If one is not found, use |
| 183 // a safe default, POLICY_DENY_ERROR. |
| 184 const BootstrapSandboxPolicy* policy = sandbox_->PolicyForProcess(sender_pid); |
| 185 const BootstrapSandboxPolicy::const_iterator it = |
| 186 policy->find(request_service_name); |
| 187 Rule rule(POLICY_DENY_ERROR); |
| 188 if (it != policy->end()) |
| 189 rule = it->second; |
| 190 |
| 191 if (rule.result == POLICY_ALLOW) { |
| 192 // This service is explicitly allowed, so this message will not be |
| 193 // intercepted by the sandbox. |
| 194 VLOG(1) << "Permitting and forwarding look_up2: " << request_service_name; |
| 195 ForwardMessage(request, reply); |
| 196 } else if (rule.result == POLICY_DENY_ERROR) { |
| 197 // The child is not permitted to look up this service. Send a MIG error |
| 198 // reply to the client. Returning a NULL or unserviced port for a look up |
| 199 // can cause clients to crash or hang. |
| 200 VLOG(1) << "Denying look_up2 with MIG error: " << request_service_name; |
| 201 RejectMessage(request, reply, BOOTSTRAP_UNKNOWN_SERVICE); |
| 202 } else if (rule.result == POLICY_DENY_DUMMY_PORT || |
| 203 rule.result == POLICY_SUBSTITUTE_PORT) { |
| 204 // The policy result is to deny access to the real service port, replying |
| 205 // with a sandboxed port in its stead. Use either the dummy sandbox_port_ |
| 206 // or the one specified in the policy. |
| 207 VLOG(1) << "Intercepting look_up2 with a sandboxed service port: " |
| 208 << request_service_name; |
| 209 |
| 210 mach_port_t result_port; |
| 211 if (rule.result == POLICY_DENY_DUMMY_PORT) |
| 212 result_port = sandbox_port_.get(); |
| 213 else |
| 214 result_port = rule.substitute_port; |
| 215 |
| 216 // Grant an additional send right on the result_port so that it can be |
| 217 // sent to the sandboxed child process. |
| 218 kern_return_t kr = mach_port_insert_right(mach_task_self(), |
| 219 result_port, result_port, MACH_MSG_TYPE_MAKE_SEND); |
| 220 if (kr != KERN_SUCCESS) { |
| 221 MACH_LOG(ERROR, kr) << "Unable to insert right on result_port."; |
| 222 } |
| 223 |
| 224 compat_shim_.look_up2_fill_reply(reply, result_port); |
| 225 SendReply(reply); |
| 226 } else { |
| 227 NOTREACHED(); |
| 228 } |
| 229 } |
| 230 |
| 231 void LaunchdInterceptionServer::HandleSwapInteger(mach_msg_header_t* request, |
| 232 mach_msg_header_t* reply, |
| 233 pid_t sender_pid) { |
| 234 // TODO(rsesek): Crack the message and ensure that the swap is only being |
| 235 // used to get the value of a VPROC key, and do not allow setting it. |
| 236 VLOG(2) << "Forwarding vproc swap message #" << request->msgh_id; |
| 237 ForwardMessage(request, reply); |
| 238 } |
| 239 |
| 240 void LaunchdInterceptionServer::SendReply(mach_msg_header_t* reply) { |
| 241 kern_return_t kr = mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, |
| 242 MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| 243 if (kr != KERN_SUCCESS) { |
| 244 MACH_LOG(ERROR, kr) << "Unable to send intercepted reply message."; |
| 245 } |
| 246 } |
| 247 |
| 248 void LaunchdInterceptionServer::ForwardMessage(mach_msg_header_t* request, |
| 249 mach_msg_header_t* reply) { |
| 250 request->msgh_local_port = request->msgh_remote_port; |
| 251 request->msgh_remote_port = sandbox_->real_bootstrap_port(); |
| 252 // Preserve the msgh_bits that do not deal with the local and remote ports. |
| 253 request->msgh_bits = (request->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) | |
| 254 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE); |
| 255 kern_return_t kr = mach_msg_send(request); |
| 256 if (kr != KERN_SUCCESS) { |
| 257 MACH_LOG(ERROR, kr) << "Unable to forward message to the real launchd."; |
| 258 } |
| 259 } |
| 260 |
| 261 void LaunchdInterceptionServer::RejectMessage(mach_msg_header_t* request, |
| 262 mach_msg_header_t* reply, |
| 263 int error_code) { |
| 264 mig_reply_error_t* error_reply = reinterpret_cast<mig_reply_error_t*>(reply); |
| 265 error_reply->Head.msgh_size = sizeof(mig_reply_error_t); |
| 266 error_reply->Head.msgh_bits = |
| 267 MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE); |
| 268 error_reply->NDR = NDR_record; |
| 269 error_reply->RetCode = error_code; |
| 270 SendReply(&error_reply->Head); |
| 271 } |
| 272 |
| 273 } // namespace sandbox |
OLD | NEW |