Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(389)

Side by Side Diff: sandbox/mac/launchd_interception_server.cc

Issue 264923003: Initial implementation of the Mac Bootstrap Sandbox. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase for new Mach utilities Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 sandbox_port_(MACH_PORT_NULL),
27 compat_shim_(GetLaunchdCompatibilityShim()) {
28 }
29
30 LaunchdInterceptionServer::~LaunchdInterceptionServer() {
31 dispatch_release(server_source_);
32 dispatch_release(server_queue_);
33 }
34
35 bool LaunchdInterceptionServer::Initialize() {
36 mach_port_t task = mach_task_self();
37 kern_return_t kr;
38
39 // Allocate a port for use as a new bootstrap port.
40 mach_port_t port;
41 if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) !=
42 KERN_SUCCESS) {
43 MACH_LOG(ERROR, kr) << "Failed to allocate new bootstrap port.";
44 return false;
45 }
46 if ((kr = mach_port_insert_right(task, port, port, MACH_MSG_TYPE_MAKE_SEND))
47 != KERN_SUCCESS) {
48 MACH_LOG(ERROR, kr) << "Failed to insert send right on bootstrap port.";
49 return false;
50 }
51 server_port_.reset(port);
52
53 // Allocate the message request and reply buffers.
54 const int kMachMsgMemory = VM_MAKE_TAG(VM_MEMORY_MACH_MSG) | TRUE;
Mark Mentovai 2014/05/09 20:11:19 You don’t really mean TRUE, you mean VM_FLAGS_ANYW
Robert Sesek 2014/05/09 22:04:03 Done. I just copied this from some Apple code. Go
55 vm_address_t buffer = 0;
56
57 kr = vm_allocate(task, &buffer, kBufferSize, kMachMsgMemory);
58 if (kr != KERN_SUCCESS) {
59 MACH_LOG(ERROR, kr) << "Failed to allocate request buffer.";
60 return false;
61 }
62 request_buffer_.reset(buffer, kBufferSize);
Mark Mentovai 2014/05/09 20:11:19 Maybe you should have local scopers for this and .
Robert Sesek 2014/05/09 22:04:03 If Initialize fails, anything properly allocated w
Mark Mentovai 2014/05/09 22:20:52 rsesek wrote:
Robert Sesek 2014/05/09 22:29:45 Well, in Create() it explicitly is destroyed if in
63
64 kr = vm_allocate(task, &buffer, kBufferSize, kMachMsgMemory);
65 if (kr != KERN_SUCCESS) {
66 MACH_LOG(ERROR, kr) << "Failed to allocate reply buffer.";
67 return false;
68 }
69 reply_buffer_.reset(buffer, kBufferSize);
70
71 // Set up the dispatch queue to service the bootstrap port.
72 // TODO(rsesek): Specify DISPATCH_QUEUE_SERIAL, in the 10.7 SDK.
Mark Mentovai 2014/05/09 20:11:19 This scared me into thinking that serial as a beha
Robert Sesek 2014/05/09 22:04:03 Done.
73 server_queue_ = dispatch_queue_create(
74 "org.chromium.sandbox.LaunchdInterceptionServer", NULL);
75 server_source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV,
76 server_port_.get(), 0, server_queue_);
77 dispatch_source_set_event_handler(server_source_, ^{ ReceiveMessage(); });
78 dispatch_resume(server_source_);
79
80 // Allocate the dummy sandbox port.
81 if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) !=
82 KERN_SUCCESS) {
83 MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port.";
84 return false;
85 }
86 sandbox_port_.reset(port);
87
88 return true;
89 }
90
91 void LaunchdInterceptionServer::ReceiveMessage() {
92 const mach_msg_options_t kRcvOptions = MACH_RCV_MSG |
93 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
94 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
95
96 mach_msg_header_t* request =
97 reinterpret_cast<mach_msg_header_t*>(request_buffer_.address());
98 mach_msg_header_t* reply =
99 reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address());
100
101 // Zero out the buffers from handling any previous message.
102 bzero(request, kBufferSize);
103 bzero(reply, kBufferSize);
104
105 // A Mach message server-once. The system library to run a message server
106 // cannot be used here, because some requests are conditionally forwarded
107 // to another server.
108 kern_return_t kr = mach_msg(request, kRcvOptions, 0, kBufferSize,
109 server_port_.get(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
110 if (kr != KERN_SUCCESS) {
111 MACH_LOG(ERROR, kr) << "Unable to receive message.";
112 return;
113 }
114
115 // Set up a reply message in case it will be used.
116 reply->msgh_bits = MACH_MSGH_BITS_REMOTE(reply->msgh_bits);
117 // Since mach_msg will automatically swap the request and reply ports,
118 // undo that.
119 reply->msgh_remote_port = request->msgh_remote_port;
120 reply->msgh_local_port = MACH_PORT_NULL;
121 // MIG servers simply add 100 to the request ID to generate the reply ID.
122 reply->msgh_id = request->msgh_id + 100;
123
124 // Process the message.
125 DemuxMessage(request, reply);
126
127 // Free any descriptors in the message body.
128 mach_msg_destroy(request);
129 mach_msg_destroy(reply);
130 }
131
132 void LaunchdInterceptionServer::DemuxMessage(mach_msg_header_t* request,
133 mach_msg_header_t* reply) {
134 VLOG(3) << "Incoming message #" << request->msgh_id;
135
136 // Get the PID of the task that sent this request. This requires getting at
137 // the trailer of the message, from the header.
138 mach_msg_audit_trailer_t* trailer =
139 reinterpret_cast<mach_msg_audit_trailer_t*>(
140 reinterpret_cast<vm_address_t>(request) +
141 round_msg(request->msgh_size));
142 // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid().
143 pid_t sender_pid;
144 audit_token_to_au32(trailer->msgh_audit,
145 NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL);
146
147 if (sandbox_->PolicyForProcess(sender_pid) == NULL) {
148 // No sandbox policy is in place for the sender of this message, which
149 // means it is from the sandbox host proceess or an unsandboxed child.
Avi (use Gerrit) 2014/05/09 21:02:06 typo: process
Robert Sesek 2014/05/09 22:04:03 Done.
150 VLOG(3) << "Message from pid " << sender_pid << " forwarded to launchd";
151 ForwardMessage(request, reply);
152 return;
153 }
154
155 if (request->msgh_id == compat_shim_.msg_id_look_up2) {
156 // Filter messages sent via bootstrap_look_up to enforce the sandbox policy
157 // over the bootstrap namespace.
158 HandleLookUp(request, reply, sender_pid);
159 } else if (request->msgh_id == compat_shim_.msg_id_swap_integer) {
160 // Ensure that any vproc_swap_integer requests are safe.
161 HandleSwapInteger(request, reply, sender_pid);
162 } else {
163 // All other messages are not permitted.
164 VLOG(1) << "Rejecting unhandled message #" << request->msgh_id;
165 RejectMessage(request, reply, MIG_REMOTE_ERROR);
166 }
167 }
168
169 void LaunchdInterceptionServer::HandleLookUp(mach_msg_header_t* request,
170 mach_msg_header_t* reply,
171 pid_t sender_pid) {
172 const std::string request_service_name(
173 compat_shim_.look_up2_get_request_name(request));
174 VLOG(2) << "Incoming look_up2 request for " << request_service_name;
175
176 // Find the Rule for this service. If one is not found, use
177 // a safe default, POLICY_DENY_ERROR.
178 const BootstrapSandboxPolicy* policy = sandbox_->PolicyForProcess(sender_pid);
179 const BootstrapSandboxPolicy::const_iterator it =
180 policy->find(request_service_name);
181 Rule rule(POLICY_DENY_ERROR);
182 if (it != policy->end())
183 rule = it->second;
184
185 if (rule.result == POLICY_ALLOW) {
186 // This service is explicitly allowed, so this message will not be
187 // intercepted by the sandbox.
188 VLOG(1) << "Permitting and forwarding look_up2: " << request_service_name;
189 ForwardMessage(request, reply);
190 } else if (rule.result == POLICY_DENY_ERROR) {
191 // The child is not permitted to look up this service. Send a MIG error
192 // reply to the client. Returning a NULL or unserviced port for a look up
193 // can cause clients to crash or hang.
194 VLOG(1) << "Denying look_up2 with MIG error: " << request_service_name;
195 RejectMessage(request, reply, BOOTSTRAP_UNKNOWN_SERVICE);
196 } else if (rule.result == POLICY_DENY_DUMMY_PORT ||
197 rule.result == POLICY_SUBSTITUE_PORT) {
198 // The policy result is to deny access to the real service port, replying
199 // with a sandboxed port in its stead. Use either the dummy sandbox_port_
200 // or the one specified in the policy.
201 VLOG(1) << "Intercepting look_up2 with a sandboxed service port: "
202 << request_service_name;
203
204 mach_port_t result_port;
205 if (rule.result == POLICY_DENY_DUMMY_PORT)
206 result_port = sandbox_port_.get();
207 else
208 result_port = rule.substitute_port;
209
210 // Grant an additional send right on the result_port so that it can be
211 // sent to the sandboxed child process.
212 kern_return_t kr = mach_port_insert_right(mach_task_self(),
213 result_port, result_port, MACH_MSG_TYPE_MAKE_SEND);
214 if (kr != KERN_SUCCESS) {
215 MACH_LOG(ERROR, kr) << "Unable to insert right on result_port.";
216 }
217
218 compat_shim_.look_up2_fill_reply(reply, result_port);
219 SendReply(reply);
220 } else {
221 NOTREACHED();
222 }
223 }
224
225 void LaunchdInterceptionServer::HandleSwapInteger(mach_msg_header_t* request,
226 mach_msg_header_t* reply,
227 pid_t sender_pid) {
228 // TODO(rsesek): Crack the message and ensure that the swap is only being
Mark Mentovai 2014/05/09 20:11:19 Cool.
229 // used to get the value of a VPROC key, and do not allow setting it.
230 VLOG(2) << "Forwarding vproc swap message #" << request->msgh_id;
231 ForwardMessage(request, reply);
232 }
233
234 void LaunchdInterceptionServer::SendReply(mach_msg_header_t* reply) {
235 kern_return_t kr = mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0,
236 MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
237 if (kr != KERN_SUCCESS) {
238 MACH_LOG(ERROR, kr) << "Unable to send intercepted reply message.";
239 }
240 }
241
242 void LaunchdInterceptionServer::ForwardMessage(mach_msg_header_t* request,
243 mach_msg_header_t* reply) {
244 request->msgh_local_port = request->msgh_remote_port;
245 request->msgh_remote_port = sandbox_->real_bootstrap_port();
246 // Preserve the msgh_bits that do not deal with the local and remote ports.
247 request->msgh_bits = (request->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) |
248 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE);
249 kern_return_t kr = mach_msg_send(request);
250 if (kr != KERN_SUCCESS) {
251 MACH_LOG(ERROR, kr) << "Unable to forward message to the real launchd.";
252 }
253 }
254
255 void LaunchdInterceptionServer::RejectMessage(mach_msg_header_t* request,
256 mach_msg_header_t* reply,
257 int error_code) {
258 mig_reply_error_t* error_reply = reinterpret_cast<mig_reply_error_t*>(reply);
259 error_reply->Head.msgh_size = sizeof(mig_reply_error_t);
260 error_reply->Head.msgh_bits =
261 MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE);
262 error_reply->NDR = NDR_record;
263 error_reply->RetCode = error_code;
264 SendReply(&error_reply->Head);
265 }
266
267 } // namespace sandbox
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698