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 
9 #include <stddef.h>
10 
11 #include <utility>
12 
13 #include "base/check.h"
14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/notreached.h"
17 #include "base/threading/platform_thread.h"
18 #include "base/win/scoped_handle.h"
19 #include "base/win/scoped_process_information.h"
20 #include "base/win/windows_version.h"
21 #include "build/build_config.h"
22 #include "sandbox/win/src/app_container_profile.h"
23 #include "sandbox/win/src/process_mitigations.h"
24 #include "sandbox/win/src/sandbox.h"
25 #include "sandbox/win/src/sandbox_policy_base.h"
26 #include "sandbox/win/src/sandbox_policy_diagnostic.h"
27 #include "sandbox/win/src/startup_information_helper.h"
28 #include "sandbox/win/src/target_process.h"
29 #include "sandbox/win/src/win2k_threadpool.h"
30 #include "sandbox/win/src/win_utils.h"
31 
32 namespace {
33 
34 // Utility function to associate a completion port to a job object.
AssociateCompletionPort(HANDLE job,HANDLE port,void * key)35 bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) {
36   JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = {key, port};
37   return ::SetInformationJobObject(job,
38                                    JobObjectAssociateCompletionPortInformation,
39                                    &job_acp, sizeof(job_acp))
40              ? true
41              : false;
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_NEW_JOB_TRACKER,
49   THREAD_CTRL_NEW_PROCESS_TRACKER,
50   THREAD_CTRL_PROCESS_SIGNALLED,
51   THREAD_CTRL_GET_POLICY_INFO,
52   THREAD_CTRL_QUIT,
53   THREAD_CTRL_LAST,
54 };
55 
56 // Helper structure that allows the Broker to associate a job notification
57 // with a job object and with a policy.
58 struct JobTracker {
JobTracker__anon18965db30111::JobTracker59   JobTracker(base::win::ScopedHandle job,
60              scoped_refptr<sandbox::PolicyBase> policy,
61              DWORD process_id)
62       : job(std::move(job)), policy(policy), process_id(process_id) {}
~JobTracker__anon18965db30111::JobTracker63   ~JobTracker() {
64     // As if TerminateProcess() was called for all associated processes.
65     // Handles are still valid.
66     ::TerminateJobObject(job.Get(), sandbox::SBOX_ALL_OK);
67     policy->OnJobEmpty(job.Get());
68   }
69 
70   base::win::ScopedHandle job;
71   scoped_refptr<sandbox::PolicyBase> policy;
72   DWORD process_id;
73 };
74 
75 // Tracks processes that are not in jobs.
76 struct ProcessTracker {
ProcessTracker__anon18965db30111::ProcessTracker77   ProcessTracker(scoped_refptr<sandbox::PolicyBase> policy,
78                  DWORD process_id,
79                  base::win::ScopedHandle process)
80       : policy(policy), process_id(process_id), process(std::move(process)) {}
~ProcessTracker__anon18965db30111::ProcessTracker81   ~ProcessTracker() {
82     // Removes process from the policy.
83     policy->OnProcessFinished(process_id);
84   }
85 
86   scoped_refptr<sandbox::PolicyBase> policy;
87   DWORD process_id;
88   base::win::ScopedHandle process;
89   // Used to UnregisterWait. Not a real handle so cannot CloseHandle().
90   HANDLE wait_handle;
91   // IOCP that is tracking this non-job process
92   HANDLE iocp;
93 };
94 
95 // Helper redispatches process events to tracker thread.
ProcessEventCallback(PVOID param,BOOLEAN ignored)96 void WINAPI ProcessEventCallback(PVOID param, BOOLEAN ignored) {
97   // This callback should do very little, and must be threadpool safe.
98   ProcessTracker* tracker = reinterpret_cast<ProcessTracker*>(param);
99   // If this fails we can do nothing... we will leak the policy.
100   ::PostQueuedCompletionStatus(tracker->iocp, 0, THREAD_CTRL_PROCESS_SIGNALLED,
101                                reinterpret_cast<LPOVERLAPPED>(tracker));
102 }
103 
104 // Helper class to send policy lists
105 class PolicyDiagnosticList final : public sandbox::PolicyList {
106  public:
PolicyDiagnosticList()107   PolicyDiagnosticList() {}
~PolicyDiagnosticList()108   ~PolicyDiagnosticList() override {}
push_back(std::unique_ptr<sandbox::PolicyInfo> info)109   void push_back(std::unique_ptr<sandbox::PolicyInfo> info) {
110     internal_list_.push_back(std::move(info));
111   }
begin()112   std::vector<std::unique_ptr<sandbox::PolicyInfo>>::iterator begin() override {
113     return internal_list_.begin();
114   }
end()115   std::vector<std::unique_ptr<sandbox::PolicyInfo>>::iterator end() override {
116     return internal_list_.end();
117   }
size() const118   size_t size() const override { return internal_list_.size(); }
119 
120  private:
121   std::vector<std::unique_ptr<sandbox::PolicyInfo>> internal_list_;
122 };
123 
124 }  // namespace
125 
126 namespace sandbox {
127 
BrokerServicesBase()128 BrokerServicesBase::BrokerServicesBase() {}
129 
130 // The broker uses a dedicated worker thread that services the job completion
131 // port to perform policy notifications and associated cleanup tasks.
Init()132 ResultCode BrokerServicesBase::Init() {
133   if (job_port_.IsValid() || thread_pool_)
134     return SBOX_ERROR_UNEXPECTED_CALL;
135 
136   job_port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0));
137   if (!job_port_.IsValid())
138     return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES;
139 
140   no_targets_.Set(::CreateEventW(nullptr, true, false, nullptr));
141 
142 #if defined(ARCH_CPU_32_BITS)
143   // Conserve address space in 32-bit Chrome. This thread uses a small and
144   // consistent amount and doesn't need the default of 1.5 MiB.
145   constexpr unsigned flags = STACK_SIZE_PARAM_IS_A_RESERVATION;
146   constexpr size_t stack_size = 128 * 1024;
147 #else
148   constexpr unsigned int flags = 0;
149   constexpr size_t stack_size = 0;
150 #endif
151   job_thread_.Set(::CreateThread(nullptr, stack_size,  // Default security.
152                                  TargetEventsThread, this, flags, nullptr));
153   if (!job_thread_.IsValid())
154     return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES;
155 
156   return SBOX_ALL_OK;
157 }
158 
159 // The destructor should only be called when the Broker process is terminating.
160 // Since BrokerServicesBase is a singleton, this is called from the CRT
161 // termination handlers, if this code lives on a DLL it is called during
162 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
163 // wait for threads here.
~BrokerServicesBase()164 BrokerServicesBase::~BrokerServicesBase() {
165   // If there is no port Init() was never called successfully.
166   if (!job_port_.IsValid())
167     return;
168 
169   // Closing the port causes, that no more Job notifications are delivered to
170   // the worker thread and also causes the thread to exit. This is what we
171   // want to do since we are going to close all outstanding Jobs and notifying
172   // the policy objects ourselves.
173   ::PostQueuedCompletionStatus(job_port_.Get(), 0, THREAD_CTRL_QUIT, nullptr);
174 
175   if (job_thread_.IsValid() &&
176       WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_.Get(), 1000)) {
177     // Cannot clean broker services.
178     NOTREACHED();
179     return;
180   }
181   thread_pool_.reset();
182 }
183 
CreatePolicy()184 scoped_refptr<TargetPolicy> BrokerServicesBase::CreatePolicy() {
185   // If you change the type of the object being created here you must also
186   // change the downcast to it in SpawnTarget().
187   scoped_refptr<TargetPolicy> policy(new PolicyBase);
188   // PolicyBase starts with refcount 1.
189   policy->Release();
190   return policy;
191 }
192 
193 // The worker thread stays in a loop waiting for asynchronous notifications
194 // from the job objects. Right now we only care about knowing when the last
195 // process on a job terminates, but in general this is the place to tell
196 // the policy about events.
TargetEventsThread(PVOID param)197 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
198   if (!param)
199     return 1;
200 
201   base::PlatformThread::SetName("BrokerEvent");
202 
203   BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
204   HANDLE port = broker->job_port_.Get();
205   HANDLE no_targets = broker->no_targets_.Get();
206 
207   std::set<DWORD> child_process_ids;
208   std::list<std::unique_ptr<JobTracker>> jobs;
209   std::list<std::unique_ptr<ProcessTracker>> processes;
210   int target_counter = 0;
211   int untracked_target_counter = 0;
212   ::ResetEvent(no_targets);
213 
214   while (true) {
215     DWORD events = 0;
216     ULONG_PTR key = 0;
217     LPOVERLAPPED ovl = nullptr;
218 
219     if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) {
220       // this call fails if the port has been closed before we have a
221       // chance to service the last packet which is 'exit' anyway so
222       // this is not an error.
223       return 1;
224     }
225 
226     if (key > THREAD_CTRL_LAST) {
227       // The notification comes from a job object. There are nine notifications
228       // that jobs can send and some of them depend on the job attributes set.
229       JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
230 
231       // Processes may be added to a job after the process count has
232       // reached zero, leading us to manipulate a freed JobTracker
233       // object or job handle (as the key is no longer valid). We
234       // therefore check if the tracker has already been deleted.
235       if (std::find_if(jobs.begin(), jobs.end(), [&](auto&& p) -> bool {
236             return p.get() == tracker;
237           }) == jobs.end()) {
238         CHECK(false);
239       }
240 
241       switch (events) {
242         case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
243           // The job object has signaled that the last process associated
244           // with it has terminated. It is safe to free the tracker
245           // and release its reference to the associated policy object
246           // which will Close the job handle.
247           HANDLE job_handle = tracker->job.Get();
248 
249           // Erase by comparing with the job handle.
250           jobs.erase(std::remove_if(jobs.begin(), jobs.end(),
251                                     [&](auto&& p) -> bool {
252                                       return p->job.Get() == job_handle;
253                                     }),
254                      jobs.end());
255           break;
256         }
257 
258         case JOB_OBJECT_MSG_NEW_PROCESS: {
259           // Child process created from sandboxed process.
260           DWORD process_id =
261               static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl));
262           size_t count = child_process_ids.count(process_id);
263           if (count == 0)
264             untracked_target_counter++;
265           ++target_counter;
266           if (1 == target_counter) {
267             ::ResetEvent(no_targets);
268           }
269           break;
270         }
271 
272         case JOB_OBJECT_MSG_EXIT_PROCESS:
273         case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
274           size_t erase_result = child_process_ids.erase(
275               static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
276           if (erase_result != 1U) {
277             // The process was untracked e.g. a child process of the target.
278             --untracked_target_counter;
279             DCHECK(untracked_target_counter >= 0);
280           }
281           --target_counter;
282           if (0 == target_counter)
283             ::SetEvent(no_targets);
284 
285           DCHECK(target_counter >= 0);
286           break;
287         }
288 
289         case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
290           // A child process attempted and failed to create a child process.
291           // Windows does not reveal the process id.
292           untracked_target_counter++;
293           target_counter++;
294           break;
295         }
296 
297         case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
298           bool res = ::TerminateJobObject(tracker->job.Get(),
299                                           SBOX_FATAL_MEMORY_EXCEEDED);
300           DCHECK(res);
301           break;
302         }
303 
304         default: {
305           NOTREACHED();
306           break;
307         }
308       }
309     } else if (THREAD_CTRL_NEW_JOB_TRACKER == key) {
310       std::unique_ptr<JobTracker> tracker;
311       tracker.reset(reinterpret_cast<JobTracker*>(ovl));
312       DCHECK(tracker->job.IsValid());
313 
314       child_process_ids.insert(tracker->process_id);
315       jobs.push_back(std::move(tracker));
316 
317     } else if (THREAD_CTRL_NEW_PROCESS_TRACKER == key) {
318       std::unique_ptr<ProcessTracker> tracker;
319       tracker.reset(reinterpret_cast<ProcessTracker*>(ovl));
320 
321       if (child_process_ids.empty()) {
322         ::SetEvent(broker->no_targets_.Get());
323       }
324 
325       tracker->iocp = port;
326       if (!::RegisterWaitForSingleObject(&(tracker->wait_handle),
327                                          tracker->process.Get(),
328                                          ProcessEventCallback, tracker.get(),
329                                          INFINITE, WT_EXECUTEONLYONCE)) {
330         // Failed. Invalidate the wait_handle and store anyway.
331         tracker->wait_handle = INVALID_HANDLE_VALUE;
332       }
333       processes.push_back(std::move(tracker));
334 
335     } else if (THREAD_CTRL_PROCESS_SIGNALLED == key) {
336       ProcessTracker* tracker =
337           static_cast<ProcessTracker*>(reinterpret_cast<void*>(ovl));
338 
339       ::UnregisterWait(tracker->wait_handle);
340       tracker->wait_handle = INVALID_HANDLE_VALUE;
341       // Copy process_id so that we can legally reference it even after we have
342       // found the ProcessTracker object to delete.
343       const DWORD process_id = tracker->process_id;
344       // PID is unique until the process handle is closed in dtor.
345       processes.erase(std::remove_if(processes.begin(), processes.end(),
346                                      [&](auto&& p) -> bool {
347                                        return p->process_id == process_id;
348                                      }),
349                       processes.end());
350 
351     } else if (THREAD_CTRL_GET_POLICY_INFO == key) {
352       // Clone the policies for sandbox diagnostics.
353       std::unique_ptr<PolicyDiagnosticsReceiver> receiver;
354       receiver.reset(static_cast<PolicyDiagnosticsReceiver*>(
355           reinterpret_cast<void*>(ovl)));
356       // The PollicyInfo ctor copies essential information from the trackers.
357       auto policy_list = std::make_unique<PolicyDiagnosticList>();
358       for (auto&& process_tracker : processes) {
359         if (process_tracker->policy) {
360           policy_list->push_back(std::make_unique<PolicyDiagnostic>(
361               process_tracker->policy.get()));
362         }
363       }
364       for (auto&& job_tracker : jobs) {
365         if (job_tracker->policy) {
366           policy_list->push_back(
367               std::make_unique<PolicyDiagnostic>(job_tracker->policy.get()));
368         }
369       }
370       // Receiver should return quickly.
371       receiver->ReceiveDiagnostics(std::move(policy_list));
372 
373     } else if (THREAD_CTRL_QUIT == key) {
374       // The broker object is being destroyed so the thread needs to exit.
375       for (auto&& tracker : processes) {
376         ::UnregisterWait(tracker->wait_handle);
377         tracker->wait_handle = INVALID_HANDLE_VALUE;
378       }
379       // After this point, so further calls to ProcessEventCallback can
380       // occur. Other tracked objects are destroyed as this thread ends.
381       return 0;
382     } else {
383       // We have not implemented more commands.
384       NOTREACHED();
385     }
386   }
387 
388   NOTREACHED();
389   return 0;
390 }
391 
392 // SpawnTarget does all the interesting sandbox setup and creates the target
393 // process inside the sandbox.
SpawnTarget(const wchar_t * exe_path,const wchar_t * command_line,scoped_refptr<TargetPolicy> policy,ResultCode * last_warning,DWORD * last_error,PROCESS_INFORMATION * target_info)394 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
395                                            const wchar_t* command_line,
396                                            scoped_refptr<TargetPolicy> policy,
397                                            ResultCode* last_warning,
398                                            DWORD* last_error,
399                                            PROCESS_INFORMATION* target_info) {
400   if (!exe_path)
401     return SBOX_ERROR_BAD_PARAMS;
402 
403   if (!policy)
404     return SBOX_ERROR_BAD_PARAMS;
405 
406   // Even though the resources touched by SpawnTarget can be accessed in
407   // multiple threads, the method itself cannot be called from more than one
408   // thread. This is to protect the global variables used while setting up the
409   // child process, and to make sure launcher thread mitigations are applied
410   // correctly.
411   static DWORD thread_id = ::GetCurrentThreadId();
412   DCHECK(thread_id == ::GetCurrentThreadId());
413   *last_warning = SBOX_ALL_OK;
414 
415   // Launcher thread only needs to be opted out of ACG once. Do this on the
416   // first child process being spawned.
417   static bool launcher_thread_opted_out = false;
418 
419   if (!launcher_thread_opted_out) {
420     // Soft fail this call. It will fail if ACG is not enabled for this process.
421     sandbox::ApplyMitigationsToCurrentThread(
422         sandbox::MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD);
423     launcher_thread_opted_out = true;
424   }
425 
426   // This downcast is safe as long as we control CreatePolicy()
427   scoped_refptr<PolicyBase> policy_base(static_cast<PolicyBase*>(policy.get()));
428 
429   // Construct the tokens and the job object that we are going to associate
430   // with the soon to be created target process.
431   base::win::ScopedHandle initial_token;
432   base::win::ScopedHandle lockdown_token;
433   base::win::ScopedHandle lowbox_token;
434   ResultCode result = SBOX_ALL_OK;
435 
436   result =
437       policy_base->MakeTokens(&initial_token, &lockdown_token, &lowbox_token);
438   if (SBOX_ALL_OK != result)
439     return result;
440   if (lowbox_token.IsValid() &&
441       base::win::GetVersion() < base::win::Version::WIN8) {
442     // We don't allow lowbox_token below Windows 8.
443     return SBOX_ERROR_BAD_PARAMS;
444   }
445 
446   base::win::ScopedHandle job;
447   result = policy_base->MakeJobObject(&job);
448   if (SBOX_ALL_OK != result)
449     return result;
450 
451   // Initialize the startup information from the policy.
452   auto startup_info = std::make_unique<StartupInformationHelper>();
453 
454   // We don't want any child processes causing the IDC_APPSTARTING cursor.
455   startup_info->UpdateFlags(STARTF_FORCEOFFFEEDBACK);
456   startup_info->SetDesktop(policy_base->GetAlternateDesktop());
457   startup_info->SetMitigations(policy_base->GetProcessMitigations());
458 
459   if (base::win::GetVersion() >= base::win::Version::WIN10_TH2 &&
460       policy_base->GetJobLevel() <= JOB_LIMITED_USER) {
461     startup_info->SetRestrictChildProcessCreation(true);
462   }
463 
464   // Shares std handles if they are valid.
465   startup_info->SetStdHandles(policy_base->GetStdoutHandle(),
466                               policy_base->GetStderrHandle());
467   // Add any additional handles that were requested.
468   const auto& policy_handle_list = policy_base->GetHandlesBeingShared();
469   for (HANDLE handle : policy_handle_list)
470     startup_info->AddInheritedHandle(handle);
471 
472   scoped_refptr<AppContainerProfileBase> profile =
473       policy_base->GetAppContainerProfileBase();
474   if (profile)
475     startup_info->SetAppContainerProfile(profile);
476 
477   // On Win10, jobs are associated via startup_info.
478   if (base::win::GetVersion() >= base::win::Version::WIN10 && job.IsValid()) {
479     startup_info->AddJobToAssociate(job.Get());
480   }
481 
482   if (!startup_info->BuildStartupInformation())
483     return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
484 
485   // Construct the thread pool here in case it is expensive.
486   // The thread pool is shared by all the targets
487   if (!thread_pool_)
488     thread_pool_ = std::make_unique<Win2kThreadPool>();
489 
490   // Create the TargetProcess object and spawn the target suspended. Note that
491   // Brokerservices does not own the target object. It is owned by the Policy.
492   base::win::ScopedProcessInformation process_info;
493   std::unique_ptr<TargetProcess> target = std::make_unique<TargetProcess>(
494       std::move(initial_token), std::move(lockdown_token), job.Get(),
495       thread_pool_.get(),
496       profile ? profile->GetImpersonationCapabilities() : std::vector<Sid>());
497 
498   result = target->Create(exe_path, command_line, std::move(startup_info),
499                           &process_info, last_error);
500 
501   if (result != SBOX_ALL_OK) {
502     target->Terminate();
503     return result;
504   }
505 
506   if (job.IsValid() && policy_base->GetJobLevel() <= JOB_LIMITED_USER) {
507     // Restrict the job from containing any processes. Job restrictions
508     // are only applied at process creation, so the target process is
509     // unaffected.
510     result = policy_base->DropActiveProcessLimit(&job);
511     if (result != SBOX_ALL_OK) {
512       target->Terminate();
513       return result;
514     }
515   }
516 
517   if (lowbox_token.IsValid()) {
518     *last_warning = target->AssignLowBoxToken(lowbox_token);
519     // If this fails we continue, but report the error as a warning.
520     // This is due to certain configurations causing the setting of the
521     // token to fail post creation, and we'd rather continue if possible.
522     if (*last_warning != SBOX_ALL_OK)
523       *last_error = ::GetLastError();
524   }
525 
526   // Now the policy is the owner of the target. TargetProcess will terminate
527   // the process if it has not completed when it is destroyed.
528   result = policy_base->AddTarget(std::move(target));
529 
530   if (result != SBOX_ALL_OK) {
531     *last_error = ::GetLastError();
532     return result;
533   }
534 
535   if (job.IsValid()) {
536     JobTracker* tracker =
537         new JobTracker(std::move(job), policy_base, process_info.process_id());
538 
539     // Post the tracker to the tracking thread, then associate the job with
540     // the tracker. The worker thread takes ownership of these objects.
541     CHECK(::PostQueuedCompletionStatus(
542         job_port_.Get(), 0, THREAD_CTRL_NEW_JOB_TRACKER,
543         reinterpret_cast<LPOVERLAPPED>(tracker)));
544     // There is no obvious cleanup here.
545     CHECK(
546         AssociateCompletionPort(tracker->job.Get(), job_port_.Get(), tracker));
547   } else {
548     // Duplicate the process handle to give the tracking machinery
549     // something valid to wait on in the tracking thread.
550     HANDLE tmp_process_handle = INVALID_HANDLE_VALUE;
551     if (!::DuplicateHandle(::GetCurrentProcess(), process_info.process_handle(),
552                            ::GetCurrentProcess(), &tmp_process_handle,
553                            SYNCHRONIZE, false, 0 /*no options*/)) {
554       *last_error = ::GetLastError();
555       return SBOX_ERROR_CANNOT_DUPLICATE_PROCESS_HANDLE;
556     }
557     base::win::ScopedHandle dup_process_handle(tmp_process_handle);
558     ProcessTracker* tracker = new ProcessTracker(
559         policy_base, process_info.process_id(), std::move(dup_process_handle));
560     // The worker thread takes ownership of the policy.
561     CHECK(::PostQueuedCompletionStatus(
562         job_port_.Get(), 0, THREAD_CTRL_NEW_PROCESS_TRACKER,
563         reinterpret_cast<LPOVERLAPPED>(tracker)));
564   }
565 
566   *target_info = process_info.Take();
567   return result;
568 }
569 
WaitForAllTargets()570 ResultCode BrokerServicesBase::WaitForAllTargets() {
571   ::WaitForSingleObject(no_targets_.Get(), INFINITE);
572   return SBOX_ALL_OK;
573 }
574 
GetPolicyDiagnostics(std::unique_ptr<PolicyDiagnosticsReceiver> receiver)575 ResultCode BrokerServicesBase::GetPolicyDiagnostics(
576     std::unique_ptr<PolicyDiagnosticsReceiver> receiver) {
577   CHECK(job_thread_.IsValid());
578   // Post to the job thread.
579   if (!::PostQueuedCompletionStatus(
580           job_port_.Get(), 0, THREAD_CTRL_GET_POLICY_INFO,
581           reinterpret_cast<LPOVERLAPPED>(receiver.get()))) {
582     receiver->OnError(SBOX_ERROR_GENERIC);
583     return SBOX_ERROR_GENERIC;
584   }
585 
586   // Ownership has passed to tracker thread.
587   receiver.release();
588   return SBOX_ALL_OK;
589 }
590 
591 }  // namespace sandbox
592