OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // This file implements the Windows service controlling Me2Me host processes | 5 // This file implements the Windows service controlling Me2Me host processes |
6 // running within user sessions. | 6 // running within user sessions. |
7 | 7 |
8 #include "remoting/host/wts_session_process_launcher_win.h" | 8 #include "remoting/host/wts_session_process_launcher_win.h" |
9 | 9 |
10 #include <windows.h> | 10 #include <windows.h> |
| 11 #include <sddl.h> |
| 12 #include <limits> |
11 | 13 |
12 #include "base/logging.h" | 14 #include "base/logging.h" |
| 15 #include "base/process_util.h" |
| 16 #include "base/rand_util.h" |
| 17 #include "base/string16.h" |
| 18 #include "base/stringprintf.h" |
| 19 #include "base/threading/thread.h" |
13 #include "base/utf_string_conversions.h" | 20 #include "base/utf_string_conversions.h" |
14 #include "base/win/scoped_handle.h" | 21 #include "base/win/scoped_handle.h" |
| 22 #include "ipc/ipc_channel_proxy.h" |
| 23 #include "ipc/ipc_message.h" |
| 24 #include "ipc/ipc_message_macros.h" |
15 | 25 |
| 26 #include "remoting/host/chromoting_messages.h" |
| 27 #include "remoting/host/sas_injector.h" |
16 #include "remoting/host/wts_console_monitor_win.h" | 28 #include "remoting/host/wts_console_monitor_win.h" |
17 | 29 |
18 using base::win::ScopedHandle; | 30 using base::win::ScopedHandle; |
19 using base::TimeDelta; | 31 using base::TimeDelta; |
20 | 32 |
21 namespace { | 33 namespace { |
22 | 34 |
23 // The minimum and maximum delays between attempts to inject host process into | 35 // The minimum and maximum delays between attempts to inject host process into |
24 // a session. | 36 // a session. |
25 const int kMaxLaunchDelaySeconds = 60; | 37 const int kMaxLaunchDelaySeconds = 60; |
26 const int kMinLaunchDelaySeconds = 1; | 38 const int kMinLaunchDelaySeconds = 1; |
27 | 39 |
28 // Name of the default session desktop. | 40 // Name of the default session desktop. |
29 const char kDefaultDesktopName[] = "winsta0\\default"; | 41 const char kDefaultDesktopName[] = "winsta0\\default"; |
30 | 42 |
| 43 // Match the pipe name prefix used by Chrome IPC channels. |
| 44 const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome."; |
| 45 |
| 46 // Generates the command line of the host process. |
| 47 const char kHostProcessCommandLineFormat[] = "\"%ls\" --channel=%ls"; |
| 48 |
| 49 // The security descriptor of the Chromoting IPC channel. It gives full access |
| 50 // to LocalSystem and denies access by anyone else. |
| 51 const char kChromotingChannelSecurityDescriptor[] = |
| 52 "O:SY" "G:SY" "D:(A;;GA;;;SY)"; |
| 53 |
31 // Takes the process token and makes a copy of it. The returned handle will have | 54 // Takes the process token and makes a copy of it. The returned handle will have |
32 // |desired_access| rights. | 55 // |desired_access| rights. |
33 bool CopyProcessToken(DWORD desired_access, | 56 bool CopyProcessToken(DWORD desired_access, |
34 ScopedHandle* token_out) { | 57 ScopedHandle* token_out) { |
35 | 58 |
36 HANDLE handle; | 59 HANDLE handle; |
37 if (!OpenProcessToken(GetCurrentProcess(), | 60 if (!OpenProcessToken(GetCurrentProcess(), |
38 TOKEN_DUPLICATE | desired_access, | 61 TOKEN_DUPLICATE | desired_access, |
39 &handle)) { | 62 &handle)) { |
40 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; | 63 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 sizeof(new_session_id))) { | 130 sizeof(new_session_id))) { |
108 LOG_GETLASTERROR(ERROR) << | 131 LOG_GETLASTERROR(ERROR) << |
109 "Failed to change session ID of a token"; | 132 "Failed to change session ID of a token"; |
110 return false; | 133 return false; |
111 } | 134 } |
112 | 135 |
113 token_out->Set(session_token.Take()); | 136 token_out->Set(session_token.Take()); |
114 return true; | 137 return true; |
115 } | 138 } |
116 | 139 |
| 140 // Generates random channel ID. |
| 141 // N.B. Stolen from src/content/common/child_process_host_impl.cc |
| 142 string16 GenerateRandomChannelId(void* instance) { |
| 143 return base::StringPrintf(ASCIIToUTF16("%d.%p.%d").c_str(), |
| 144 base::GetCurrentProcId(), instance, |
| 145 base::RandInt(0, std::numeric_limits<int>::max())); |
| 146 } |
| 147 |
| 148 // Creates the server end of the Chromoting IPC channel. |
| 149 // N.B. This code is based on IPC::Channel's implementation. |
| 150 bool CreatePipeForIpcChannel(void* instance, |
| 151 string16* channel_name_out, |
| 152 ScopedHandle* pipe_out) { |
| 153 // Create security descriptor for the channel. |
| 154 SECURITY_ATTRIBUTES security_attributes; |
| 155 security_attributes.nLength = sizeof(security_attributes); |
| 156 security_attributes.bInheritHandle = FALSE; |
| 157 |
| 158 ULONG security_descriptor_length = 0; |
| 159 if (!ConvertStringSecurityDescriptorToSecurityDescriptorA( |
| 160 kChromotingChannelSecurityDescriptor, |
| 161 SDDL_REVISION_1, |
| 162 reinterpret_cast<PSECURITY_DESCRIPTOR*>( |
| 163 &security_attributes.lpSecurityDescriptor), |
| 164 &security_descriptor_length)) { |
| 165 LOG_GETLASTERROR(ERROR) << |
| 166 "Failed to create a security descriptor for the Chromoting IPC channel"; |
| 167 return false; |
| 168 } |
| 169 |
| 170 // Generate a random channel name. |
| 171 string16 channel_name(GenerateRandomChannelId(instance)); |
| 172 |
| 173 // Convert it to the pipe name. |
| 174 string16 pipe_name(ASCIIToUTF16(kChromePipeNamePrefix)); |
| 175 pipe_name.append(channel_name); |
| 176 |
| 177 // Create the server end of the pipe. This code should match the code in |
| 178 // IPC::Channel with exception of passing a non-default security descriptor. |
| 179 HANDLE pipe = CreateNamedPipeW(pipe_name.c_str(), |
| 180 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | |
| 181 FILE_FLAG_FIRST_PIPE_INSTANCE, |
| 182 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, |
| 183 1, |
| 184 IPC::Channel::kReadBufferSize, |
| 185 IPC::Channel::kReadBufferSize, |
| 186 5000, |
| 187 &security_attributes); |
| 188 if (pipe == INVALID_HANDLE_VALUE) { |
| 189 LOG_GETLASTERROR(ERROR) << |
| 190 "Failed to create the server end of the Chromoting IPC channel"; |
| 191 LocalFree(security_attributes.lpSecurityDescriptor); |
| 192 return false; |
| 193 } |
| 194 |
| 195 LocalFree(security_attributes.lpSecurityDescriptor); |
| 196 |
| 197 *channel_name_out = channel_name; |
| 198 pipe_out->Set(pipe); |
| 199 return true; |
| 200 } |
| 201 |
117 // Launches |binary| in the security context of the supplied |user_token|. | 202 // Launches |binary| in the security context of the supplied |user_token|. |
118 bool LaunchProcessAsUser(const FilePath& binary, | 203 bool LaunchProcessAsUser(const FilePath& binary, |
| 204 const string16& command_line, |
119 HANDLE user_token, | 205 HANDLE user_token, |
120 base::Process* process_out) { | 206 base::Process* process_out) { |
121 string16 command_line = binary.value(); | 207 string16 application_name = binary.value(); |
122 string16 desktop = ASCIIToUTF16(kDefaultDesktopName); | 208 string16 desktop = ASCIIToUTF16(kDefaultDesktopName); |
123 | 209 |
124 PROCESS_INFORMATION process_info; | 210 PROCESS_INFORMATION process_info; |
125 STARTUPINFOW startup_info; | 211 STARTUPINFOW startup_info; |
126 | 212 |
127 memset(&startup_info, 0, sizeof(startup_info)); | 213 memset(&startup_info, 0, sizeof(startup_info)); |
128 startup_info.cb = sizeof(startup_info); | 214 startup_info.cb = sizeof(startup_info); |
129 startup_info.lpDesktop = const_cast<LPWSTR>(desktop.c_str()); | 215 startup_info.lpDesktop = const_cast<LPWSTR>(desktop.c_str()); |
130 | 216 |
131 if (!CreateProcessAsUserW(user_token, | 217 if (!CreateProcessAsUserW(user_token, |
132 command_line.c_str(), | 218 application_name.c_str(), |
133 const_cast<LPWSTR>(command_line.c_str()), | 219 const_cast<LPWSTR>(command_line.c_str()), |
134 NULL, | 220 NULL, |
135 NULL, | 221 NULL, |
136 FALSE, | 222 FALSE, |
137 0, | 223 0, |
138 NULL, | 224 NULL, |
139 NULL, | 225 NULL, |
140 &startup_info, | 226 &startup_info, |
141 &process_info)) { | 227 &process_info)) { |
142 LOG_GETLASTERROR(ERROR) << | 228 LOG_GETLASTERROR(ERROR) << |
143 "Failed to launch a process with a user token"; | 229 "Failed to launch a process with a user token"; |
144 return false; | 230 return false; |
145 } | 231 } |
146 | 232 |
147 CloseHandle(process_info.hThread); | 233 CloseHandle(process_info.hThread); |
148 process_out->set_handle(process_info.hProcess); | 234 process_out->set_handle(process_info.hProcess); |
149 return true; | 235 return true; |
150 } | 236 } |
151 | 237 |
152 } // namespace | 238 } // namespace |
153 | 239 |
154 namespace remoting { | 240 namespace remoting { |
155 | 241 |
156 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | 242 WtsSessionProcessLauncher::WtsSessionProcessLauncher( |
157 WtsConsoleMonitor* monitor, | 243 WtsConsoleMonitor* monitor, |
158 const FilePath& host_binary) | 244 const FilePath& host_binary, |
| 245 base::Thread* io_thread) |
159 : host_binary_(host_binary), | 246 : host_binary_(host_binary), |
| 247 io_thread_(io_thread), |
160 monitor_(monitor), | 248 monitor_(monitor), |
161 state_(StateDetached) { | 249 state_(StateDetached) { |
162 monitor_->AddWtsConsoleObserver(this); | 250 monitor_->AddWtsConsoleObserver(this); |
163 } | 251 } |
164 | 252 |
165 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | 253 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
166 DCHECK(state_ == StateDetached); | 254 DCHECK(state_ == StateDetached); |
167 DCHECK(!timer_.IsRunning()); | 255 DCHECK(!timer_.IsRunning()); |
168 DCHECK(process_.handle() == NULL); | 256 DCHECK(process_.handle() == NULL); |
169 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 257 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
| 258 DCHECK(chromoting_channel_.get() == NULL); |
170 | 259 |
171 monitor_->RemoveWtsConsoleObserver(this); | 260 monitor_->RemoveWtsConsoleObserver(this); |
172 } | 261 } |
173 | 262 |
174 void WtsSessionProcessLauncher::LaunchProcess() { | 263 void WtsSessionProcessLauncher::LaunchProcess() { |
175 DCHECK(state_ == StateStarting); | 264 DCHECK(state_ == StateStarting); |
176 DCHECK(!timer_.IsRunning()); | 265 DCHECK(!timer_.IsRunning()); |
177 DCHECK(process_.handle() == NULL); | 266 DCHECK(process_.handle() == NULL); |
178 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 267 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
| 268 DCHECK(chromoting_channel_.get() == NULL); |
179 | 269 |
180 // Try to launch the process and attach an object watcher to the returned | |
181 // handle so that we get notified when the process terminates. | |
182 launch_time_ = base::Time::Now(); | 270 launch_time_ = base::Time::Now(); |
183 if (LaunchProcessAsUser(host_binary_, session_token_, &process_)) { | 271 |
184 if (process_watcher_.StartWatching(process_.handle(), this)) { | 272 string16 channel_name; |
185 state_ = StateAttached; | 273 ScopedHandle pipe; |
186 return; | 274 if (CreatePipeForIpcChannel(this, &channel_name, &pipe)) { |
187 } else { | 275 // Wrap the pipe into an IPC channel. |
188 LOG(ERROR) << "Failed to arm the process watcher."; | 276 chromoting_channel_.reset(new IPC::ChannelProxy( |
189 process_.Terminate(0); | 277 IPC::ChannelHandle(pipe.Get()), |
190 process_.Close(); | 278 IPC::Channel::MODE_SERVER, |
| 279 this, |
| 280 io_thread_->message_loop_proxy().get())); |
| 281 |
| 282 string16 command_line = |
| 283 base::StringPrintf(ASCIIToUTF16(kHostProcessCommandLineFormat).c_str(), |
| 284 host_binary_.value().c_str(), |
| 285 channel_name.c_str()); |
| 286 |
| 287 // Try to launch the process and attach an object watcher to the returned |
| 288 // handle so that we get notified when the process terminates. |
| 289 if (LaunchProcessAsUser(host_binary_, command_line, session_token_, |
| 290 &process_)) { |
| 291 if (process_watcher_.StartWatching(process_.handle(), this)) { |
| 292 state_ = StateAttached; |
| 293 return; |
| 294 } else { |
| 295 LOG(ERROR) << "Failed to arm the process watcher."; |
| 296 process_.Terminate(0); |
| 297 process_.Close(); |
| 298 } |
191 } | 299 } |
| 300 |
| 301 chromoting_channel_.reset(); |
192 } | 302 } |
193 | 303 |
194 // Something went wrong. Try to launch the host again later. The attempts rate | 304 // Something went wrong. Try to launch the host again later. The attempts rate |
195 // is limited by exponential backoff. | 305 // is limited by exponential backoff. |
196 launch_backoff_ = std::max(launch_backoff_ * 2, | 306 launch_backoff_ = std::max(launch_backoff_ * 2, |
197 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | 307 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); |
198 launch_backoff_ = std::min(launch_backoff_, | 308 launch_backoff_ = std::min(launch_backoff_, |
199 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | 309 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); |
200 timer_.Start(FROM_HERE, launch_backoff_, | 310 timer_.Start(FROM_HERE, launch_backoff_, |
201 this, &WtsSessionProcessLauncher::LaunchProcess); | 311 this, &WtsSessionProcessLauncher::LaunchProcess); |
202 } | 312 } |
203 | 313 |
204 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { | 314 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { |
205 DCHECK(state_ == StateAttached); | 315 DCHECK(state_ == StateAttached); |
206 DCHECK(!timer_.IsRunning()); | 316 DCHECK(!timer_.IsRunning()); |
207 DCHECK(process_.handle() != NULL); | 317 DCHECK(process_.handle() != NULL); |
208 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 318 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
| 319 DCHECK(chromoting_channel_.get() != NULL); |
209 | 320 |
210 // The host process has been terminated for some reason. The handle can now be | 321 // The host process has been terminated for some reason. The handle can now be |
211 // closed. | 322 // closed. |
212 process_.Close(); | 323 process_.Close(); |
| 324 chromoting_channel_.reset(); |
213 | 325 |
214 // Expand the backoff interval if the process has died quickly or reset it if | 326 // Expand the backoff interval if the process has died quickly or reset it if |
215 // it was up longer than the maximum backoff delay. | 327 // it was up longer than the maximum backoff delay. |
216 base::TimeDelta delta = base::Time::Now() - launch_time_; | 328 base::TimeDelta delta = base::Time::Now() - launch_time_; |
217 if (delta < base::TimeDelta() || | 329 if (delta < base::TimeDelta() || |
218 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { | 330 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { |
219 launch_backoff_ = base::TimeDelta(); | 331 launch_backoff_ = base::TimeDelta(); |
220 } else { | 332 } else { |
221 launch_backoff_ = std::max(launch_backoff_ * 2, | 333 launch_backoff_ = std::max(launch_backoff_ * 2, |
222 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | 334 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); |
223 launch_backoff_ = std::min(launch_backoff_, | 335 launch_backoff_ = std::min(launch_backoff_, |
224 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | 336 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); |
225 } | 337 } |
226 | 338 |
227 // Try to restart the host. | 339 // Try to restart the host. |
228 state_ = StateStarting; | 340 state_ = StateStarting; |
229 timer_.Start(FROM_HERE, launch_backoff_, | 341 timer_.Start(FROM_HERE, launch_backoff_, |
230 this, &WtsSessionProcessLauncher::LaunchProcess); | 342 this, &WtsSessionProcessLauncher::LaunchProcess); |
231 } | 343 } |
232 | 344 |
| 345 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { |
| 346 bool handled = true; |
| 347 IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message) |
| 348 IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole, |
| 349 OnSendSasToConsole) |
| 350 IPC_MESSAGE_UNHANDLED(handled = false) |
| 351 IPC_END_MESSAGE_MAP() |
| 352 return handled; |
| 353 } |
| 354 |
| 355 void WtsSessionProcessLauncher::OnSendSasToConsole() { |
| 356 if (state_ == StateAttached) { |
| 357 if (sas_injector_.get() == NULL) { |
| 358 sas_injector_ = SasInjector::Create(); |
| 359 } |
| 360 |
| 361 if (sas_injector_.get() != NULL) { |
| 362 sas_injector_->InjectSas(); |
| 363 } |
| 364 } |
| 365 } |
| 366 |
233 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | 367 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { |
234 DCHECK(state_ == StateDetached); | 368 DCHECK(state_ == StateDetached); |
235 DCHECK(!timer_.IsRunning()); | 369 DCHECK(!timer_.IsRunning()); |
236 DCHECK(process_.handle() == NULL); | 370 DCHECK(process_.handle() == NULL); |
237 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 371 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
| 372 DCHECK(chromoting_channel_.get() == NULL); |
238 | 373 |
239 // Temporarily enable the SE_TCB_NAME privilege. The privileged token is | 374 // Temporarily enable the SE_TCB_NAME privilege. The privileged token is |
240 // created as needed and kept for later reuse. | 375 // created as needed and kept for later reuse. |
241 if (privileged_token_.Get() == NULL) { | 376 if (privileged_token_.Get() == NULL) { |
242 if (!CreatePrivilegedToken(&privileged_token_)) { | 377 if (!CreatePrivilegedToken(&privileged_token_)) { |
243 return; | 378 return; |
244 } | 379 } |
245 } | 380 } |
246 | 381 |
247 if (!ImpersonateLoggedOnUser(privileged_token_)) { | 382 if (!ImpersonateLoggedOnUser(privileged_token_)) { |
248 LOG_GETLASTERROR(ERROR) << | 383 LOG_GETLASTERROR(ERROR) << |
249 "Failed to impersonate the privileged token"; | 384 "Failed to impersonate the privileged token"; |
250 return; | 385 return; |
251 } | 386 } |
252 | 387 |
253 // While the SE_TCB_NAME progolege is enabled, create a session token for | 388 // While the SE_TCB_NAME privilege is enabled, create a session token for |
254 // the launched process. | 389 // the launched process. |
255 bool result = CreateSessionToken(session_id, &session_token_); | 390 bool result = CreateSessionToken(session_id, &session_token_); |
256 | 391 |
257 // Revert to the default token. The default token is sufficient to call | 392 // Revert to the default token. The default token is sufficient to call |
258 // CreateProcessAsUser() successfully. | 393 // CreateProcessAsUser() successfully. |
259 CHECK(RevertToSelf()); | 394 CHECK(RevertToSelf()); |
260 | 395 |
261 if (!result) | 396 if (!result) |
262 return; | 397 return; |
263 | 398 |
264 // Now try to launch the host. | 399 // Now try to launch the host. |
265 state_ = StateStarting; | 400 state_ = StateStarting; |
266 LaunchProcess(); | 401 LaunchProcess(); |
267 } | 402 } |
268 | 403 |
269 void WtsSessionProcessLauncher::OnSessionDetached() { | 404 void WtsSessionProcessLauncher::OnSessionDetached() { |
270 DCHECK(state_ == StateDetached || | 405 DCHECK(state_ == StateDetached || |
271 state_ == StateStarting || | 406 state_ == StateStarting || |
272 state_ == StateAttached); | 407 state_ == StateAttached); |
273 | 408 |
274 switch (state_) { | 409 switch (state_) { |
275 case StateDetached: | 410 case StateDetached: |
276 DCHECK(!timer_.IsRunning()); | 411 DCHECK(!timer_.IsRunning()); |
277 DCHECK(process_.handle() == NULL); | 412 DCHECK(process_.handle() == NULL); |
278 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 413 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
| 414 DCHECK(chromoting_channel_.get() == NULL); |
279 break; | 415 break; |
280 | 416 |
281 case StateStarting: | 417 case StateStarting: |
282 DCHECK(timer_.IsRunning()); | 418 DCHECK(timer_.IsRunning()); |
283 DCHECK(process_.handle() == NULL); | 419 DCHECK(process_.handle() == NULL); |
284 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 420 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
| 421 DCHECK(chromoting_channel_.get() == NULL); |
285 | 422 |
286 timer_.Stop(); | 423 timer_.Stop(); |
287 launch_backoff_ = base::TimeDelta(); | 424 launch_backoff_ = base::TimeDelta(); |
288 state_ = StateDetached; | 425 state_ = StateDetached; |
289 break; | 426 break; |
290 | 427 |
291 case StateAttached: | 428 case StateAttached: |
292 DCHECK(!timer_.IsRunning()); | 429 DCHECK(!timer_.IsRunning()); |
293 DCHECK(process_.handle() != NULL); | 430 DCHECK(process_.handle() != NULL); |
294 DCHECK(process_watcher_.GetWatchedObject() != NULL); | 431 DCHECK(process_watcher_.GetWatchedObject() != NULL); |
| 432 DCHECK(chromoting_channel_.get() != NULL); |
295 | 433 |
296 process_watcher_.StopWatching(); | 434 process_watcher_.StopWatching(); |
297 process_.Terminate(0); | 435 process_.Terminate(0); |
298 process_.Close(); | 436 process_.Close(); |
| 437 chromoting_channel_.reset(); |
299 state_ = StateDetached; | 438 state_ = StateDetached; |
300 break; | 439 break; |
301 } | 440 } |
302 } | 441 } |
303 | 442 |
304 } // namespace remoting | 443 } // namespace remoting |
OLD | NEW |