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