OLD | NEW |
| (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 // This file implements the Windows service controlling Me2Me host processes | |
6 // running within user sessions. | |
7 | |
8 #include "remoting/host/wts_session_process_launcher_win.h" | |
9 | |
10 #include <windows.h> | |
11 #include <sddl.h> | |
12 #include <limits> | |
13 | |
14 #include "base/base_switches.h" | |
15 #include "base/bind.h" | |
16 #include "base/bind_helpers.h" | |
17 #include "base/command_line.h" | |
18 #include "base/logging.h" | |
19 #include "base/message_loop_proxy.h" | |
20 #include "base/process_util.h" | |
21 #include "base/rand_util.h" | |
22 #include "base/stringprintf.h" | |
23 #include "base/win/scoped_handle.h" | |
24 #include "ipc/ipc_channel_proxy.h" | |
25 #include "ipc/ipc_message.h" | |
26 #include "ipc/ipc_message_macros.h" | |
27 #include "remoting/host/constants.h" | |
28 #include "remoting/host/chromoting_messages.h" | |
29 #include "remoting/host/launch_process_in_session_win.h" | |
30 #include "remoting/host/sas_injector.h" | |
31 #include "remoting/host/wts_console_monitor_win.h" | |
32 | |
33 using base::win::ScopedHandle; | |
34 using base::TimeDelta; | |
35 | |
36 namespace { | |
37 | |
38 // The minimum and maximum delays between attempts to inject host process into | |
39 // a session. | |
40 const int kMaxLaunchDelaySeconds = 60; | |
41 const int kMinLaunchDelaySeconds = 1; | |
42 | |
43 // Match the pipe name prefix used by Chrome IPC channels. | |
44 const wchar_t kChromePipeNamePrefix[] = L"\\\\.\\pipe\\chrome."; | |
45 | |
46 // The IPC channel name is passed to the host in the command line. | |
47 const char kChromotingIpcSwitchName[] = "chromoting-ipc"; | |
48 | |
49 // The command line parameters that should be copied from the service's command | |
50 // line to the host process. | |
51 const char* kCopiedSwitchNames[] = { | |
52 "auth-config", "host-config", switches::kV, switches::kVModule }; | |
53 | |
54 // The security descriptor of the Chromoting IPC channel. It gives full access | |
55 // to LocalSystem and denies access by anyone else. | |
56 const wchar_t kChromotingChannelSecurityDescriptor[] = | |
57 L"O:SYG:SYD:(A;;GA;;;SY)"; | |
58 | |
59 // Takes the process token and makes a copy of it. The returned handle will have | |
60 // |desired_access| rights. | |
61 bool CopyProcessToken(DWORD desired_access, | |
62 ScopedHandle* token_out) { | |
63 | |
64 HANDLE handle; | |
65 if (!OpenProcessToken(GetCurrentProcess(), | |
66 TOKEN_DUPLICATE | desired_access, | |
67 &handle)) { | |
68 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; | |
69 return false; | |
70 } | |
71 | |
72 ScopedHandle process_token(handle); | |
73 | |
74 if (!DuplicateTokenEx(process_token, | |
75 desired_access, | |
76 NULL, | |
77 SecurityImpersonation, | |
78 TokenPrimary, | |
79 &handle)) { | |
80 LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token"; | |
81 return false; | |
82 } | |
83 | |
84 token_out->Set(handle); | |
85 return true; | |
86 } | |
87 | |
88 // Creates a copy of the current process with SE_TCB_NAME privilege enabled. | |
89 bool CreatePrivilegedToken(ScopedHandle* token_out) { | |
90 ScopedHandle privileged_token; | |
91 DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE | | |
92 TOKEN_DUPLICATE | TOKEN_QUERY; | |
93 if (!CopyProcessToken(desired_access, &privileged_token)) { | |
94 return false; | |
95 } | |
96 | |
97 // Get the LUID for the SE_TCB_NAME privilege. | |
98 TOKEN_PRIVILEGES state; | |
99 state.PrivilegeCount = 1; | |
100 state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
101 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) { | |
102 LOG_GETLASTERROR(ERROR) << | |
103 "Failed to lookup the LUID for the SE_TCB_NAME privilege"; | |
104 return false; | |
105 } | |
106 | |
107 // Enable the SE_TCB_NAME privilege. | |
108 if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) { | |
109 LOG_GETLASTERROR(ERROR) << | |
110 "Failed to enable SE_TCB_NAME privilege in a token"; | |
111 return false; | |
112 } | |
113 | |
114 token_out->Set(privileged_token.Take()); | |
115 return true; | |
116 } | |
117 | |
118 // Creates a copy of the current process token for the given |session_id| so | |
119 // it can be used to launch a process in that session. | |
120 bool CreateSessionToken(uint32 session_id, | |
121 ScopedHandle* token_out) { | |
122 | |
123 ScopedHandle session_token; | |
124 DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | | |
125 TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY; | |
126 if (!CopyProcessToken(desired_access, &session_token)) { | |
127 return false; | |
128 } | |
129 | |
130 // Change the session ID of the token. | |
131 DWORD new_session_id = session_id; | |
132 if (!SetTokenInformation(session_token, | |
133 TokenSessionId, | |
134 &new_session_id, | |
135 sizeof(new_session_id))) { | |
136 LOG_GETLASTERROR(ERROR) << | |
137 "Failed to change session ID of a token"; | |
138 return false; | |
139 } | |
140 | |
141 token_out->Set(session_token.Take()); | |
142 return true; | |
143 } | |
144 | |
145 // Generates random channel ID. | |
146 // N.B. Stolen from src/content/common/child_process_host_impl.cc | |
147 std::wstring GenerateRandomChannelId(void* instance) { | |
148 return base::StringPrintf(L"%d.%p.%d", | |
149 base::GetCurrentProcId(), instance, | |
150 base::RandInt(0, std::numeric_limits<int>::max())); | |
151 } | |
152 | |
153 // Creates the server end of the Chromoting IPC channel. | |
154 // N.B. This code is based on IPC::Channel's implementation. | |
155 bool CreatePipeForIpcChannel(void* instance, | |
156 std::wstring* channel_name_out, | |
157 ScopedHandle* pipe_out) { | |
158 // Create security descriptor for the channel. | |
159 SECURITY_ATTRIBUTES security_attributes; | |
160 security_attributes.nLength = sizeof(security_attributes); | |
161 security_attributes.bInheritHandle = FALSE; | |
162 | |
163 ULONG security_descriptor_length = 0; | |
164 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( | |
165 kChromotingChannelSecurityDescriptor, | |
166 SDDL_REVISION_1, | |
167 reinterpret_cast<PSECURITY_DESCRIPTOR*>( | |
168 &security_attributes.lpSecurityDescriptor), | |
169 &security_descriptor_length)) { | |
170 LOG_GETLASTERROR(ERROR) << | |
171 "Failed to create a security descriptor for the Chromoting IPC channel"; | |
172 return false; | |
173 } | |
174 | |
175 // Generate a random channel name. | |
176 std::wstring channel_name(GenerateRandomChannelId(instance)); | |
177 | |
178 // Convert it to the pipe name. | |
179 std::wstring pipe_name(kChromePipeNamePrefix); | |
180 pipe_name.append(channel_name); | |
181 | |
182 // Create the server end of the pipe. This code should match the code in | |
183 // IPC::Channel with exception of passing a non-default security descriptor. | |
184 HANDLE pipe = CreateNamedPipeW(pipe_name.c_str(), | |
185 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | | |
186 FILE_FLAG_FIRST_PIPE_INSTANCE, | |
187 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, | |
188 1, | |
189 IPC::Channel::kReadBufferSize, | |
190 IPC::Channel::kReadBufferSize, | |
191 5000, | |
192 &security_attributes); | |
193 if (pipe == INVALID_HANDLE_VALUE) { | |
194 LOG_GETLASTERROR(ERROR) << | |
195 "Failed to create the server end of the Chromoting IPC channel"; | |
196 LocalFree(security_attributes.lpSecurityDescriptor); | |
197 return false; | |
198 } | |
199 | |
200 LocalFree(security_attributes.lpSecurityDescriptor); | |
201 | |
202 *channel_name_out = channel_name; | |
203 pipe_out->Set(pipe); | |
204 return true; | |
205 } | |
206 | |
207 } // namespace | |
208 | |
209 namespace remoting { | |
210 | |
211 // Session id that does not represent any session. | |
212 const uint32 kInvalidSessionId = 0xffffffff; | |
213 | |
214 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | |
215 WtsConsoleMonitor* monitor, | |
216 const FilePath& host_binary, | |
217 scoped_refptr<base::MessageLoopProxy> main_message_loop, | |
218 scoped_refptr<base::MessageLoopProxy> ipc_message_loop) | |
219 : host_binary_(host_binary), | |
220 main_message_loop_(main_message_loop), | |
221 ipc_message_loop_(ipc_message_loop), | |
222 monitor_(monitor), | |
223 state_(StateDetached) { | |
224 monitor_->AddWtsConsoleObserver(this); | |
225 } | |
226 | |
227 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | |
228 DCHECK(state_ == StateDetached); | |
229 DCHECK(!timer_.IsRunning()); | |
230 DCHECK(process_.handle() == NULL); | |
231 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
232 DCHECK(chromoting_channel_.get() == NULL); | |
233 if (monitor_ != NULL) { | |
234 monitor_->RemoveWtsConsoleObserver(this); | |
235 } | |
236 } | |
237 | |
238 void WtsSessionProcessLauncher::LaunchProcess() { | |
239 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
240 DCHECK(state_ == StateStarting); | |
241 DCHECK(!timer_.IsRunning()); | |
242 DCHECK(process_.handle() == NULL); | |
243 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
244 DCHECK(chromoting_channel_.get() == NULL); | |
245 | |
246 launch_time_ = base::Time::Now(); | |
247 | |
248 std::wstring channel_name; | |
249 ScopedHandle pipe; | |
250 if (CreatePipeForIpcChannel(this, &channel_name, &pipe)) { | |
251 // Wrap the pipe into an IPC channel. | |
252 chromoting_channel_.reset(new IPC::ChannelProxy( | |
253 IPC::ChannelHandle(pipe.Get()), | |
254 IPC::Channel::MODE_SERVER, | |
255 this, | |
256 ipc_message_loop_)); | |
257 | |
258 // Create the host process command line passing the name of the IPC channel | |
259 // to use and copying known switches from the service's command line. | |
260 CommandLine command_line(host_binary_); | |
261 command_line.AppendSwitchNative(kChromotingIpcSwitchName, channel_name); | |
262 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), | |
263 kCopiedSwitchNames, | |
264 _countof(kCopiedSwitchNames)); | |
265 | |
266 // Try to launch the process and attach an object watcher to the returned | |
267 // handle so that we get notified when the process terminates. | |
268 if (LaunchProcessInSession(host_binary_, | |
269 command_line.GetCommandLineString(), | |
270 session_token_, | |
271 &process_)) { | |
272 if (process_watcher_.StartWatching(process_.handle(), this)) { | |
273 state_ = StateAttached; | |
274 return; | |
275 } else { | |
276 LOG(ERROR) << "Failed to arm the process watcher."; | |
277 process_.Terminate(0); | |
278 process_.Close(); | |
279 } | |
280 } | |
281 | |
282 chromoting_channel_.reset(); | |
283 } | |
284 | |
285 // Something went wrong. Try to launch the host again later. The attempts rate | |
286 // is limited by exponential backoff. | |
287 launch_backoff_ = std::max(launch_backoff_ * 2, | |
288 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
289 launch_backoff_ = std::min(launch_backoff_, | |
290 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
291 timer_.Start(FROM_HERE, launch_backoff_, | |
292 this, &WtsSessionProcessLauncher::LaunchProcess); | |
293 } | |
294 | |
295 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { | |
296 if (!main_message_loop_->BelongsToCurrentThread()) { | |
297 main_message_loop_->PostTask( | |
298 FROM_HERE, base::Bind(&WtsSessionProcessLauncher::OnObjectSignaled, | |
299 base::Unretained(this), object)); | |
300 return; | |
301 } | |
302 | |
303 // It is possible that OnObjectSignaled() task will be queued by another | |
304 // thread right before |process_watcher_| was stopped. It such a case it is | |
305 // safe to ignore this notification. | |
306 if (state_ != StateAttached) { | |
307 return; | |
308 } | |
309 | |
310 DCHECK(!timer_.IsRunning()); | |
311 DCHECK(process_.handle() != NULL); | |
312 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
313 DCHECK(chromoting_channel_.get() != NULL); | |
314 | |
315 // Stop trying to restart the host if its process exited due to | |
316 // misconfiguration. | |
317 int exit_code; | |
318 bool stop_trying = | |
319 base::WaitForExitCodeWithTimeout( | |
320 process_.handle(), &exit_code, base::TimeDelta()) && | |
321 kMinPermanentErrorExitCode <= exit_code && | |
322 exit_code <= kMaxPermanentErrorExitCode; | |
323 | |
324 // The host process has been terminated for some reason. The handle can now be | |
325 // closed. | |
326 process_.Close(); | |
327 chromoting_channel_.reset(); | |
328 state_ = StateStarting; | |
329 | |
330 if (stop_trying) { | |
331 OnSessionDetached(); | |
332 | |
333 // N.B. The service will stop once the last observer is removed from | |
334 // the list. | |
335 monitor_->RemoveWtsConsoleObserver(this); | |
336 monitor_ = NULL; | |
337 return; | |
338 } | |
339 | |
340 // Expand the backoff interval if the process has died quickly or reset it if | |
341 // it was up longer than the maximum backoff delay. | |
342 base::TimeDelta delta = base::Time::Now() - launch_time_; | |
343 if (delta < base::TimeDelta() || | |
344 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { | |
345 launch_backoff_ = base::TimeDelta(); | |
346 } else { | |
347 launch_backoff_ = std::max(launch_backoff_ * 2, | |
348 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
349 launch_backoff_ = std::min(launch_backoff_, | |
350 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
351 } | |
352 | |
353 // Try to restart the host. | |
354 timer_.Start(FROM_HERE, launch_backoff_, | |
355 this, &WtsSessionProcessLauncher::LaunchProcess); | |
356 } | |
357 | |
358 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { | |
359 bool handled = true; | |
360 IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message) | |
361 IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole, | |
362 OnSendSasToConsole) | |
363 IPC_MESSAGE_UNHANDLED(handled = false) | |
364 IPC_END_MESSAGE_MAP() | |
365 return handled; | |
366 } | |
367 | |
368 void WtsSessionProcessLauncher::OnSendSasToConsole() { | |
369 if (!main_message_loop_->BelongsToCurrentThread()) { | |
370 main_message_loop_->PostTask( | |
371 FROM_HERE, base::Bind(&WtsSessionProcessLauncher::OnSendSasToConsole, | |
372 base::Unretained(this))); | |
373 return; | |
374 } | |
375 | |
376 if (state_ == StateAttached) { | |
377 if (sas_injector_.get() == NULL) { | |
378 sas_injector_ = SasInjector::Create(); | |
379 } | |
380 | |
381 if (sas_injector_.get() != NULL) { | |
382 sas_injector_->InjectSas(); | |
383 } | |
384 } | |
385 } | |
386 | |
387 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | |
388 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
389 DCHECK(state_ == StateDetached); | |
390 DCHECK(!timer_.IsRunning()); | |
391 DCHECK(process_.handle() == NULL); | |
392 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
393 DCHECK(chromoting_channel_.get() == NULL); | |
394 | |
395 // Temporarily enable the SE_TCB_NAME privilege. The privileged token is | |
396 // created as needed and kept for later reuse. | |
397 if (privileged_token_.Get() == NULL) { | |
398 if (!CreatePrivilegedToken(&privileged_token_)) { | |
399 return; | |
400 } | |
401 } | |
402 | |
403 if (!ImpersonateLoggedOnUser(privileged_token_)) { | |
404 LOG_GETLASTERROR(ERROR) << | |
405 "Failed to impersonate the privileged token"; | |
406 return; | |
407 } | |
408 | |
409 // While the SE_TCB_NAME privilege is enabled, create a session token for | |
410 // the launched process. | |
411 bool result = CreateSessionToken(session_id, &session_token_); | |
412 | |
413 // Revert to the default token. The default token is sufficient to call | |
414 // CreateProcessAsUser() successfully. | |
415 CHECK(RevertToSelf()); | |
416 | |
417 if (!result) | |
418 return; | |
419 | |
420 // Now try to launch the host. | |
421 state_ = StateStarting; | |
422 LaunchProcess(); | |
423 } | |
424 | |
425 void WtsSessionProcessLauncher::OnSessionDetached() { | |
426 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
427 DCHECK(state_ == StateDetached || | |
428 state_ == StateStarting || | |
429 state_ == StateAttached); | |
430 | |
431 switch (state_) { | |
432 case StateDetached: | |
433 DCHECK(!timer_.IsRunning()); | |
434 DCHECK(process_.handle() == NULL); | |
435 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
436 DCHECK(chromoting_channel_.get() == NULL); | |
437 break; | |
438 | |
439 case StateStarting: | |
440 DCHECK(process_.handle() == NULL); | |
441 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
442 DCHECK(chromoting_channel_.get() == NULL); | |
443 | |
444 timer_.Stop(); | |
445 launch_backoff_ = base::TimeDelta(); | |
446 state_ = StateDetached; | |
447 break; | |
448 | |
449 case StateAttached: | |
450 DCHECK(!timer_.IsRunning()); | |
451 DCHECK(process_.handle() != NULL); | |
452 DCHECK(process_watcher_.GetWatchedObject() != NULL); | |
453 DCHECK(chromoting_channel_.get() != NULL); | |
454 | |
455 process_watcher_.StopWatching(); | |
456 process_.Terminate(0); | |
457 process_.Close(); | |
458 chromoting_channel_.reset(); | |
459 state_ = StateDetached; | |
460 break; | |
461 } | |
462 | |
463 session_token_.Close(); | |
464 } | |
465 | |
466 } // namespace remoting | |
OLD | NEW |