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

Side by Side Diff: sandbox/win/src/broker_services.cc

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

Powered by Google App Engine
This is Rietveld 408576698