|
1 // Copyright (c) 2012 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/win/src/broker_services.h" |
|
6 |
|
7 #include "base/logging.h" |
|
8 #include "base/memory/scoped_ptr.h" |
|
9 #include "base/threading/platform_thread.h" |
|
10 #include "base/win/scoped_handle.h" |
|
11 #include "base/win/scoped_process_information.h" |
|
12 #include "base/win/startup_information.h" |
|
13 #include "base/win/windows_version.h" |
|
14 #include "sandbox/win/src/app_container.h" |
|
15 #include "sandbox/win/src/process_mitigations.h" |
|
16 #include "sandbox/win/src/sandbox_policy_base.h" |
|
17 #include "sandbox/win/src/sandbox.h" |
|
18 #include "sandbox/win/src/target_process.h" |
|
19 #include "sandbox/win/src/win2k_threadpool.h" |
|
20 #include "sandbox/win/src/win_utils.h" |
|
21 |
|
22 namespace { |
|
23 |
|
24 // Utility function to associate a completion port to a job object. |
|
25 bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) { |
|
26 JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port }; |
|
27 return ::SetInformationJobObject(job, |
|
28 JobObjectAssociateCompletionPortInformation, |
|
29 &job_acp, sizeof(job_acp))? true : false; |
|
30 } |
|
31 |
|
32 // Utility function to do the cleanup necessary when something goes wrong |
|
33 // while in SpawnTarget and we must terminate the target process. |
|
34 sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) { |
|
35 if (0 == error) |
|
36 error = ::GetLastError(); |
|
37 |
|
38 target->Terminate(); |
|
39 delete target; |
|
40 ::SetLastError(error); |
|
41 return sandbox::SBOX_ERROR_GENERIC; |
|
42 } |
|
43 |
|
44 // the different commands that you can send to the worker thread that |
|
45 // executes TargetEventsThread(). |
|
46 enum { |
|
47 THREAD_CTRL_NONE, |
|
48 THREAD_CTRL_REMOVE_PEER, |
|
49 THREAD_CTRL_QUIT, |
|
50 THREAD_CTRL_LAST, |
|
51 }; |
|
52 |
|
53 // Helper structure that allows the Broker to associate a job notification |
|
54 // with a job object and with a policy. |
|
55 struct JobTracker { |
|
56 HANDLE job; |
|
57 sandbox::PolicyBase* policy; |
|
58 JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy) |
|
59 : job(cjob), policy(cpolicy) { |
|
60 } |
|
61 }; |
|
62 |
|
63 // Helper structure that allows the broker to track peer processes |
|
64 struct PeerTracker { |
|
65 HANDLE wait_object; |
|
66 base::win::ScopedHandle process; |
|
67 DWORD id; |
|
68 HANDLE job_port; |
|
69 PeerTracker(DWORD process_id, HANDLE broker_job_port) |
|
70 : wait_object(NULL), id(process_id), job_port(broker_job_port) { |
|
71 } |
|
72 }; |
|
73 |
|
74 void DeregisterPeerTracker(PeerTracker* peer) { |
|
75 // Deregistration shouldn't fail, but we leak rather than crash if it does. |
|
76 if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) { |
|
77 delete peer; |
|
78 } else { |
|
79 NOTREACHED(); |
|
80 } |
|
81 } |
|
82 |
|
83 } // namespace |
|
84 |
|
85 namespace sandbox { |
|
86 |
|
87 BrokerServicesBase::BrokerServicesBase() |
|
88 : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL), |
|
89 job_thread_(NULL) { |
|
90 } |
|
91 |
|
92 // The broker uses a dedicated worker thread that services the job completion |
|
93 // port to perform policy notifications and associated cleanup tasks. |
|
94 ResultCode BrokerServicesBase::Init() { |
|
95 if ((NULL != job_port_) || (NULL != thread_pool_)) |
|
96 return SBOX_ERROR_UNEXPECTED_CALL; |
|
97 |
|
98 ::InitializeCriticalSection(&lock_); |
|
99 |
|
100 job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); |
|
101 if (NULL == job_port_) |
|
102 return SBOX_ERROR_GENERIC; |
|
103 |
|
104 no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL); |
|
105 |
|
106 job_thread_ = ::CreateThread(NULL, 0, // Default security and stack. |
|
107 TargetEventsThread, this, NULL, NULL); |
|
108 if (NULL == job_thread_) |
|
109 return SBOX_ERROR_GENERIC; |
|
110 |
|
111 return SBOX_ALL_OK; |
|
112 } |
|
113 |
|
114 // The destructor should only be called when the Broker process is terminating. |
|
115 // Since BrokerServicesBase is a singleton, this is called from the CRT |
|
116 // termination handlers, if this code lives on a DLL it is called during |
|
117 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot |
|
118 // wait for threads here. |
|
119 BrokerServicesBase::~BrokerServicesBase() { |
|
120 // If there is no port Init() was never called successfully. |
|
121 if (!job_port_) |
|
122 return; |
|
123 |
|
124 // Closing the port causes, that no more Job notifications are delivered to |
|
125 // the worker thread and also causes the thread to exit. This is what we |
|
126 // want to do since we are going to close all outstanding Jobs and notifying |
|
127 // the policy objects ourselves. |
|
128 ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE); |
|
129 ::CloseHandle(job_port_); |
|
130 |
|
131 if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) { |
|
132 // Cannot clean broker services. |
|
133 NOTREACHED(); |
|
134 return; |
|
135 } |
|
136 |
|
137 JobTrackerList::iterator it; |
|
138 for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) { |
|
139 JobTracker* tracker = (*it); |
|
140 FreeResources(tracker); |
|
141 delete tracker; |
|
142 } |
|
143 ::CloseHandle(job_thread_); |
|
144 delete thread_pool_; |
|
145 ::CloseHandle(no_targets_); |
|
146 |
|
147 // Cancel the wait events and delete remaining peer trackers. |
|
148 for (PeerTrackerMap::iterator it = peer_map_.begin(); |
|
149 it != peer_map_.end(); ++it) { |
|
150 DeregisterPeerTracker(it->second); |
|
151 } |
|
152 |
|
153 // If job_port_ isn't NULL, assumes that the lock has been initialized. |
|
154 if (job_port_) |
|
155 ::DeleteCriticalSection(&lock_); |
|
156 } |
|
157 |
|
158 TargetPolicy* BrokerServicesBase::CreatePolicy() { |
|
159 // If you change the type of the object being created here you must also |
|
160 // change the downcast to it in SpawnTarget(). |
|
161 return new PolicyBase; |
|
162 } |
|
163 |
|
164 void BrokerServicesBase::FreeResources(JobTracker* tracker) { |
|
165 if (NULL != tracker->policy) { |
|
166 BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK); |
|
167 DCHECK(res); |
|
168 // Closing the job causes the target process to be destroyed so this |
|
169 // needs to happen before calling OnJobEmpty(). |
|
170 res = ::CloseHandle(tracker->job); |
|
171 DCHECK(res); |
|
172 // In OnJobEmpty() we don't actually use the job handle directly. |
|
173 tracker->policy->OnJobEmpty(tracker->job); |
|
174 tracker->policy->Release(); |
|
175 tracker->policy = NULL; |
|
176 } |
|
177 } |
|
178 |
|
179 // The worker thread stays in a loop waiting for asynchronous notifications |
|
180 // from the job objects. Right now we only care about knowing when the last |
|
181 // process on a job terminates, but in general this is the place to tell |
|
182 // the policy about events. |
|
183 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { |
|
184 if (NULL == param) |
|
185 return 1; |
|
186 |
|
187 base::PlatformThread::SetName("BrokerEvent"); |
|
188 |
|
189 BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param); |
|
190 HANDLE port = broker->job_port_; |
|
191 HANDLE no_targets = broker->no_targets_; |
|
192 |
|
193 int target_counter = 0; |
|
194 ::ResetEvent(no_targets); |
|
195 |
|
196 while (true) { |
|
197 DWORD events = 0; |
|
198 ULONG_PTR key = 0; |
|
199 LPOVERLAPPED ovl = NULL; |
|
200 |
|
201 if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) |
|
202 // this call fails if the port has been closed before we have a |
|
203 // chance to service the last packet which is 'exit' anyway so |
|
204 // this is not an error. |
|
205 return 1; |
|
206 |
|
207 if (key > THREAD_CTRL_LAST) { |
|
208 // The notification comes from a job object. There are nine notifications |
|
209 // that jobs can send and some of them depend on the job attributes set. |
|
210 JobTracker* tracker = reinterpret_cast<JobTracker*>(key); |
|
211 |
|
212 switch (events) { |
|
213 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { |
|
214 // The job object has signaled that the last process associated |
|
215 // with it has terminated. Assuming there is no way for a process |
|
216 // to appear out of thin air in this job, it safe to assume that |
|
217 // we can tell the policy to destroy the target object, and for |
|
218 // us to release our reference to the policy object. |
|
219 FreeResources(tracker); |
|
220 break; |
|
221 } |
|
222 |
|
223 case JOB_OBJECT_MSG_NEW_PROCESS: { |
|
224 ++target_counter; |
|
225 if (1 == target_counter) { |
|
226 ::ResetEvent(no_targets); |
|
227 } |
|
228 break; |
|
229 } |
|
230 |
|
231 case JOB_OBJECT_MSG_EXIT_PROCESS: |
|
232 case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { |
|
233 { |
|
234 AutoLock lock(&broker->lock_); |
|
235 broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl)); |
|
236 } |
|
237 --target_counter; |
|
238 if (0 == target_counter) |
|
239 ::SetEvent(no_targets); |
|
240 |
|
241 DCHECK(target_counter >= 0); |
|
242 break; |
|
243 } |
|
244 |
|
245 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { |
|
246 break; |
|
247 } |
|
248 |
|
249 default: { |
|
250 NOTREACHED(); |
|
251 break; |
|
252 } |
|
253 } |
|
254 } else if (THREAD_CTRL_REMOVE_PEER == key) { |
|
255 // Remove a process from our list of peers. |
|
256 AutoLock lock(&broker->lock_); |
|
257 PeerTrackerMap::iterator it = |
|
258 broker->peer_map_.find(reinterpret_cast<DWORD>(ovl)); |
|
259 DeregisterPeerTracker(it->second); |
|
260 broker->peer_map_.erase(it); |
|
261 } else if (THREAD_CTRL_QUIT == key) { |
|
262 // The broker object is being destroyed so the thread needs to exit. |
|
263 return 0; |
|
264 } else { |
|
265 // We have not implemented more commands. |
|
266 NOTREACHED(); |
|
267 } |
|
268 } |
|
269 |
|
270 NOTREACHED(); |
|
271 return 0; |
|
272 } |
|
273 |
|
274 // SpawnTarget does all the interesting sandbox setup and creates the target |
|
275 // process inside the sandbox. |
|
276 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, |
|
277 const wchar_t* command_line, |
|
278 TargetPolicy* policy, |
|
279 PROCESS_INFORMATION* target_info) { |
|
280 if (!exe_path) |
|
281 return SBOX_ERROR_BAD_PARAMS; |
|
282 |
|
283 if (!policy) |
|
284 return SBOX_ERROR_BAD_PARAMS; |
|
285 |
|
286 // Even though the resources touched by SpawnTarget can be accessed in |
|
287 // multiple threads, the method itself cannot be called from more than |
|
288 // 1 thread. This is to protect the global variables used while setting up |
|
289 // the child process. |
|
290 static DWORD thread_id = ::GetCurrentThreadId(); |
|
291 DCHECK(thread_id == ::GetCurrentThreadId()); |
|
292 |
|
293 AutoLock lock(&lock_); |
|
294 |
|
295 // This downcast is safe as long as we control CreatePolicy() |
|
296 PolicyBase* policy_base = static_cast<PolicyBase*>(policy); |
|
297 |
|
298 // Construct the tokens and the job object that we are going to associate |
|
299 // with the soon to be created target process. |
|
300 HANDLE initial_token_temp; |
|
301 HANDLE lockdown_token_temp; |
|
302 ResultCode result = policy_base->MakeTokens(&initial_token_temp, |
|
303 &lockdown_token_temp); |
|
304 if (SBOX_ALL_OK != result) |
|
305 return result; |
|
306 |
|
307 base::win::ScopedHandle initial_token(initial_token_temp); |
|
308 base::win::ScopedHandle lockdown_token(lockdown_token_temp); |
|
309 |
|
310 HANDLE job_temp; |
|
311 result = policy_base->MakeJobObject(&job_temp); |
|
312 if (SBOX_ALL_OK != result) |
|
313 return result; |
|
314 |
|
315 base::win::ScopedHandle job(job_temp); |
|
316 |
|
317 // Initialize the startup information from the policy. |
|
318 base::win::StartupInformation startup_info; |
|
319 string16 desktop = policy_base->GetAlternateDesktop(); |
|
320 if (!desktop.empty()) { |
|
321 startup_info.startup_info()->lpDesktop = |
|
322 const_cast<wchar_t*>(desktop.c_str()); |
|
323 } |
|
324 |
|
325 bool inherit_handles = false; |
|
326 if (base::win::GetVersion() >= base::win::VERSION_VISTA) { |
|
327 int attribute_count = 0; |
|
328 const AppContainerAttributes* app_container = |
|
329 policy_base->GetAppContainer(); |
|
330 if (app_container) |
|
331 ++attribute_count; |
|
332 |
|
333 DWORD64 mitigations; |
|
334 size_t mitigations_size; |
|
335 ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(), |
|
336 &mitigations, &mitigations_size); |
|
337 if (mitigations) |
|
338 ++attribute_count; |
|
339 |
|
340 HANDLE stdout_handle = policy_base->GetStdoutHandle(); |
|
341 HANDLE stderr_handle = policy_base->GetStderrHandle(); |
|
342 HANDLE inherit_handle_list[2]; |
|
343 int inherit_handle_count = 0; |
|
344 if (stdout_handle != INVALID_HANDLE_VALUE) |
|
345 inherit_handle_list[inherit_handle_count++] = stdout_handle; |
|
346 // Handles in the list must be unique. |
|
347 if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) |
|
348 inherit_handle_list[inherit_handle_count++] = stderr_handle; |
|
349 if (inherit_handle_count) |
|
350 ++attribute_count; |
|
351 |
|
352 if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) |
|
353 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
|
354 |
|
355 if (app_container) { |
|
356 result = app_container->ShareForStartup(&startup_info); |
|
357 if (SBOX_ALL_OK != result) |
|
358 return result; |
|
359 } |
|
360 |
|
361 if (mitigations) { |
|
362 if (!startup_info.UpdateProcThreadAttribute( |
|
363 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations, |
|
364 mitigations_size)) { |
|
365 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
|
366 } |
|
367 } |
|
368 |
|
369 if (inherit_handle_count) { |
|
370 if (!startup_info.UpdateProcThreadAttribute( |
|
371 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, |
|
372 inherit_handle_list, |
|
373 sizeof(inherit_handle_list[0]) * inherit_handle_count)) { |
|
374 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
|
375 } |
|
376 startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; |
|
377 startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; |
|
378 startup_info.startup_info()->hStdOutput = stdout_handle; |
|
379 startup_info.startup_info()->hStdError = stderr_handle; |
|
380 // Allowing inheritance of handles is only secure now that we |
|
381 // have limited which handles will be inherited. |
|
382 inherit_handles = true; |
|
383 } |
|
384 } |
|
385 |
|
386 // Construct the thread pool here in case it is expensive. |
|
387 // The thread pool is shared by all the targets |
|
388 if (NULL == thread_pool_) |
|
389 thread_pool_ = new Win2kThreadPool(); |
|
390 |
|
391 // Create the TargetProces object and spawn the target suspended. Note that |
|
392 // Brokerservices does not own the target object. It is owned by the Policy. |
|
393 base::win::ScopedProcessInformation process_info; |
|
394 TargetProcess* target = new TargetProcess(initial_token.Take(), |
|
395 lockdown_token.Take(), |
|
396 job, |
|
397 thread_pool_); |
|
398 |
|
399 DWORD win_result = target->Create(exe_path, command_line, inherit_handles, |
|
400 startup_info, &process_info); |
|
401 if (ERROR_SUCCESS != win_result) |
|
402 return SpawnCleanup(target, win_result); |
|
403 |
|
404 // Now the policy is the owner of the target. |
|
405 if (!policy_base->AddTarget(target)) { |
|
406 return SpawnCleanup(target, 0); |
|
407 } |
|
408 |
|
409 // We are going to keep a pointer to the policy because we'll call it when |
|
410 // the job object generates notifications using the completion port. |
|
411 policy_base->AddRef(); |
|
412 if (job.IsValid()) { |
|
413 scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base)); |
|
414 if (!AssociateCompletionPort(tracker->job, job_port_, tracker.get())) |
|
415 return SpawnCleanup(target, 0); |
|
416 // Save the tracker because in cleanup we might need to force closing |
|
417 // the Jobs. |
|
418 tracker_list_.push_back(tracker.release()); |
|
419 child_process_ids_.insert(process_info.process_id()); |
|
420 } else { |
|
421 // We have to signal the event once here because the completion port will |
|
422 // never get a message that this target is being terminated thus we should |
|
423 // not block WaitForAllTargets until we have at least one target with job. |
|
424 if (child_process_ids_.empty()) |
|
425 ::SetEvent(no_targets_); |
|
426 // We can not track the life time of such processes and it is responsibility |
|
427 // of the host application to make sure that spawned targets without jobs |
|
428 // are terminated when the main application don't need them anymore. |
|
429 } |
|
430 |
|
431 *target_info = process_info.Take(); |
|
432 return SBOX_ALL_OK; |
|
433 } |
|
434 |
|
435 |
|
436 ResultCode BrokerServicesBase::WaitForAllTargets() { |
|
437 ::WaitForSingleObject(no_targets_, INFINITE); |
|
438 return SBOX_ALL_OK; |
|
439 } |
|
440 |
|
441 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) { |
|
442 AutoLock lock(&lock_); |
|
443 return child_process_ids_.find(process_id) != child_process_ids_.end() || |
|
444 peer_map_.find(process_id) != peer_map_.end(); |
|
445 } |
|
446 |
|
447 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) { |
|
448 PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter); |
|
449 // Don't check the return code because we this may fail (safely) at shutdown. |
|
450 ::PostQueuedCompletionStatus(peer->job_port, 0, THREAD_CTRL_REMOVE_PEER, |
|
451 reinterpret_cast<LPOVERLAPPED>(peer->id)); |
|
452 } |
|
453 |
|
454 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { |
|
455 scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process), |
|
456 job_port_)); |
|
457 if (!peer->id) |
|
458 return SBOX_ERROR_GENERIC; |
|
459 |
|
460 HANDLE process_handle; |
|
461 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process, |
|
462 ::GetCurrentProcess(), &process_handle, |
|
463 SYNCHRONIZE, FALSE, 0)) { |
|
464 return SBOX_ERROR_GENERIC; |
|
465 } |
|
466 peer->process.Set(process_handle); |
|
467 |
|
468 AutoLock lock(&lock_); |
|
469 if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second) |
|
470 return SBOX_ERROR_BAD_PARAMS; |
|
471 |
|
472 if (!::RegisterWaitForSingleObject( |
|
473 &peer->wait_object, peer->process, RemovePeer, peer.get(), INFINITE, |
|
474 WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) { |
|
475 peer_map_.erase(peer->id); |
|
476 return SBOX_ERROR_GENERIC; |
|
477 } |
|
478 |
|
479 // Release the pointer since it will be cleaned up by the callback. |
|
480 peer.release(); |
|
481 return SBOX_ALL_OK; |
|
482 } |
|
483 |
|
484 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid, |
|
485 const wchar_t* name) { |
|
486 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) |
|
487 return SBOX_ERROR_UNSUPPORTED; |
|
488 |
|
489 string16 old_name = LookupAppContainer(sid); |
|
490 if (old_name.empty()) |
|
491 return CreateAppContainer(sid, name); |
|
492 |
|
493 if (old_name != name) |
|
494 return SBOX_ERROR_INVALID_APP_CONTAINER; |
|
495 |
|
496 return SBOX_ALL_OK; |
|
497 } |
|
498 |
|
499 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) { |
|
500 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) |
|
501 return SBOX_ERROR_UNSUPPORTED; |
|
502 |
|
503 string16 name = LookupAppContainer(sid); |
|
504 if (name.empty()) |
|
505 return SBOX_ERROR_INVALID_APP_CONTAINER; |
|
506 |
|
507 return DeleteAppContainer(sid); |
|
508 } |
|
509 |
|
510 } // namespace sandbox |