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