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 | 11 |
12 #include "base/logging.h" | 12 #include "base/logging.h" |
13 #include "base/utf_string_conversions.h" | |
14 #include "base/win/scoped_handle.h" | |
13 | 15 |
14 #include "remoting/host/wts_console_monitor_win.h" | 16 #include "remoting/host/wts_console_monitor_win.h" |
15 | 17 |
18 using base::win::ScopedHandle; | |
19 using base::TimeDelta; | |
20 | |
21 namespace { | |
22 | |
23 // The minimum and maximum delays between attempts to inject host process into | |
24 // a session. | |
25 const int kMaxLaunchDelaySeconds = 60; | |
26 const int kMinLaunchDelaySeconds = 1; | |
27 | |
28 // Name of the default session desktop. | |
29 const char kDefaultDesktopName[] = "winsta0\\default"; | |
30 | |
31 // Takes the process token and makes a copy of it. The returned handle will have | |
32 // |desired_access| rights. | |
33 bool CopyProcessToken(DWORD desired_access, | |
34 ScopedHandle* token_out) { | |
35 | |
36 HANDLE handle; | |
37 if (!OpenProcessToken(GetCurrentProcess(), | |
38 TOKEN_DUPLICATE | desired_access, | |
39 &handle)) { | |
40 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; | |
41 return false; | |
42 } | |
43 | |
44 ScopedHandle process_token(handle); | |
45 | |
46 if (!DuplicateTokenEx(process_token, | |
47 desired_access, | |
48 NULL, | |
49 SecurityImpersonation, | |
50 TokenPrimary, | |
51 &handle)) { | |
52 LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token"; | |
53 return false; | |
54 } | |
55 | |
56 token_out->Set(handle); | |
57 return true; | |
58 } | |
59 | |
60 // Creates a copy of the current process with SE_TCB_NAME privilege enabled. | |
61 bool CreatePrivilegedToken(ScopedHandle* token_out) { | |
62 ScopedHandle privileged_token; | |
63 DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE | | |
64 TOKEN_DUPLICATE | TOKEN_QUERY; | |
65 if (!CopyProcessToken(desired_access, &privileged_token)) { | |
Wez
2012/02/28 22:55:36
I'm [not all that] surprised that the compiler let
alexeypa (please no reviews)
2012/02/29 04:17:56
CopyProcessToken takes a pointer to the wrapper cl
| |
66 return false; | |
67 } | |
68 | |
69 // Get the LUID for the SE_TCB_NAME privilege. | |
70 TOKEN_PRIVILEGES state; | |
71 state.PrivilegeCount = 1; | |
72 state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
73 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) { | |
74 LOG_GETLASTERROR(ERROR) << | |
75 "Failed to lookup the LUID for the SE_TCB_NAME privilege"; | |
76 return false; | |
77 } | |
78 | |
79 // Enable the SE_TCB_NAME privilege. | |
80 if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) { | |
81 LOG_GETLASTERROR(ERROR) << | |
82 "Failed to enable SE_TCB_NAME privilege in a token"; | |
83 return false; | |
84 } | |
85 | |
86 token_out->Set(privileged_token.Take()); | |
87 return true; | |
88 } | |
89 | |
90 // Creates a copy of the current process token for the given |session_id| so | |
91 // it can be used to launch a process in that session. | |
92 bool CreateSessionToken(uint32 session_id, | |
93 ScopedHandle* token_out) { | |
94 | |
95 ScopedHandle session_token; | |
96 DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | | |
97 TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY; | |
98 if (!CopyProcessToken(desired_access, &session_token)) { | |
99 return false; | |
100 } | |
101 | |
102 // Change the session ID of the token. | |
103 DWORD new_session_id = session_id; | |
104 if (!SetTokenInformation(session_token, | |
105 TokenSessionId, | |
106 &new_session_id, | |
107 sizeof(new_session_id))) { | |
108 LOG_GETLASTERROR(ERROR) << | |
109 "Failed to change session ID of a token"; | |
110 return false; | |
111 } | |
112 | |
113 token_out->Set(session_token.Take()); | |
114 return true; | |
115 } | |
116 | |
117 // Launches |binary| in the security context of the supplied |user_token|. | |
118 bool LaunchProcessAsUser(const FilePath& binary, | |
119 HANDLE user_token, | |
120 base::Process* process_out) { | |
121 string16 command_line = binary.value(); | |
122 string16 desktop = ASCIIToUTF16(kDefaultDesktopName); | |
123 | |
124 PROCESS_INFORMATION process_info; | |
125 STARTUPINFOW startup_info; | |
126 | |
127 memset(&startup_info, 0, sizeof(startup_info)); | |
128 startup_info.cb = sizeof(startup_info); | |
129 startup_info.lpDesktop = const_cast<LPWSTR>(desktop.c_str()); | |
130 | |
131 if (!CreateProcessAsUserW(user_token, | |
132 command_line.c_str(), | |
133 const_cast<LPWSTR>(command_line.c_str()), | |
134 NULL, | |
135 NULL, | |
136 FALSE, | |
137 0, | |
138 NULL, | |
139 NULL, | |
140 &startup_info, | |
141 &process_info)) { | |
142 LOG_GETLASTERROR(ERROR) << | |
143 "Failed to launch a process with a user token"; | |
144 return false; | |
145 } | |
146 | |
147 CloseHandle(process_info.hThread); | |
Wez
2012/02/28 22:55:36
nit: Various places in the codebase use ::CloseHan
alexeypa (please no reviews)
2012/02/29 04:17:56
I can see that both variants are used in Chrome's
| |
148 process_out->set_handle(process_info.hProcess); | |
149 return true; | |
150 } | |
151 | |
152 } // namespace | |
153 | |
16 namespace remoting { | 154 namespace remoting { |
17 | 155 |
18 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | 156 WtsSessionProcessLauncher::WtsSessionProcessLauncher( |
19 WtsConsoleMonitor* monitor) : monitor_(monitor) { | 157 WtsConsoleMonitor* monitor, |
158 const FilePath& host_binary) | |
159 : host_binary_(host_binary), | |
160 monitor_(monitor), | |
161 state_(StateDetached) { | |
20 monitor_->AddWtsConsoleObserver(this); | 162 monitor_->AddWtsConsoleObserver(this); |
21 } | 163 } |
22 | 164 |
23 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | 165 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
166 DCHECK(state_ == StateDetached); | |
167 DCHECK(!timer_.IsRunning()); | |
168 DCHECK(process_.handle() == NULL); | |
169 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
170 | |
24 monitor_->RemoveWtsConsoleObserver(this); | 171 monitor_->RemoveWtsConsoleObserver(this); |
25 } | 172 } |
26 | 173 |
174 void WtsSessionProcessLauncher::LaunchProcess() { | |
175 DCHECK(state_ == StateStarting); | |
176 DCHECK(!timer_.IsRunning()); | |
177 DCHECK(process_.handle() == NULL); | |
178 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
179 | |
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(); | |
183 if (LaunchProcessAsUser(host_binary_, session_token_, &process_)) { | |
184 if (process_watcher_.StartWatching(process_.handle(), this)) { | |
185 state_ = StateAttached; | |
186 return; | |
187 } else { | |
188 LOG(ERROR) << "Failed to arm the process watcher."; | |
189 process_.Terminate(0); | |
190 process_.Close(); | |
191 } | |
192 } | |
193 | |
194 // Something went wrong. Try to launch the host again later. The attempts rate | |
195 // is limited by exponential backoff. | |
196 launch_backoff_ = std::max(launch_backoff_ * 2, | |
197 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
198 launch_backoff_ = std::min(launch_backoff_, | |
199 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
200 timer_.Start(FROM_HERE, launch_backoff_, | |
201 this, &WtsSessionProcessLauncher::LaunchProcess); | |
202 } | |
203 | |
204 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { | |
205 DCHECK(state_ == StateAttached); | |
206 DCHECK(!timer_.IsRunning()); | |
207 DCHECK(process_.handle() != NULL); | |
208 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
209 | |
210 // The host process has been terminated for some reason. The handle can now be | |
211 // closed. | |
212 process_.Close(); | |
213 | |
214 // Expand the backoff interval if the process has died quickly or reset it if | |
215 // it was up longer than the maximum backoff delay. | |
216 base::TimeDelta delta = base::Time::Now() - launch_time_; | |
217 if (delta < base::TimeDelta() || | |
218 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { | |
219 launch_backoff_ = base::TimeDelta(); | |
220 } else { | |
221 launch_backoff_ = std::max(launch_backoff_ * 2, | |
222 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
223 launch_backoff_ = std::min(launch_backoff_, | |
224 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
225 } | |
226 | |
227 // Try to restart the host. | |
228 state_ = StateStarting; | |
229 timer_.Start(FROM_HERE, launch_backoff_, | |
230 this, &WtsSessionProcessLauncher::LaunchProcess); | |
231 } | |
232 | |
27 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | 233 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { |
28 // TODO(alexeypa): The code injecting the host process to a session goes here. | 234 DCHECK(state_ == StateDetached); |
235 DCHECK(!timer_.IsRunning()); | |
236 DCHECK(process_.handle() == NULL); | |
237 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
238 | |
239 // Temporarily enable the SE_TCB_NAME privilege. The privileged token is | |
240 // created as needed and kept for later reuse. | |
241 if (privileged_token_.Get() == NULL) { | |
242 if (!CreatePrivilegedToken(&privileged_token_)) { | |
243 return; | |
244 } | |
245 } | |
246 | |
247 if (!ImpersonateLoggedOnUser(privileged_token_)) { | |
248 LOG_GETLASTERROR(ERROR) << | |
249 "Failed to impersonate the privileged token"; | |
250 return; | |
251 } | |
252 | |
253 // While the SE_TCB_NAME progolege is enabled, create a session token for | |
254 // the launched process. | |
255 bool result = CreateSessionToken(session_id, &session_token_); | |
256 | |
257 // Revert to the default token. The default token is sufficient to call | |
258 // CreateProcessAsUser() successfully. | |
259 CHECK(RevertToSelf()); | |
260 | |
261 if (!result) | |
262 return; | |
263 | |
264 // Now try to launch the host. | |
265 state_ = StateStarting; | |
266 LaunchProcess(); | |
29 } | 267 } |
30 | 268 |
31 void WtsSessionProcessLauncher::OnSessionDetached() { | 269 void WtsSessionProcessLauncher::OnSessionDetached() { |
32 // TODO(alexeypa): The code terminating the host process goes here. | 270 DCHECK(state_ == StateDetached || |
271 state_ == StateStarting || | |
272 state_ == StateAttached); | |
273 | |
274 switch (state_) { | |
275 case StateDetached: | |
276 DCHECK(!timer_.IsRunning()); | |
277 DCHECK(process_.handle() == NULL); | |
278 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
279 break; | |
280 | |
281 case StateStarting: | |
282 DCHECK(timer_.IsRunning()); | |
283 DCHECK(process_.handle() == NULL); | |
284 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
285 | |
286 timer_.Stop(); | |
287 launch_backoff_ = base::TimeDelta(); | |
288 state_ = StateDetached; | |
289 break; | |
290 | |
291 case StateAttached: | |
292 DCHECK(!timer_.IsRunning()); | |
293 DCHECK(process_.handle() != NULL); | |
294 DCHECK(process_watcher_.GetWatchedObject() != NULL); | |
295 | |
296 process_watcher_.StopWatching(); | |
297 process_.Terminate(0); | |
298 process_.Close(); | |
299 state_ = StateDetached; | |
300 break; | |
301 } | |
33 } | 302 } |
34 | 303 |
35 } // namespace remoting | 304 } // namespace remoting |
OLD | NEW |