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 scoped_ptr<ScopedHandle>* token_out) { | |
Wez
2012/02/28 00:22:07
Why does this need wrapping in a scoped_ptr<>?
alexeypa (please no reviews)
2012/02/28 01:14:04
ScopedHandle does not provide either copy or owner
| |
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 scoped_ptr<ScopedHandle> process_token; | |
45 process_token.reset(new base::win::ScopedHandle(handle)); | |
Wez
2012/02/28 00:22:07
This could become:
ScopedHandle process_token(han
alexeypa (please no reviews)
2012/02/28 01:14:04
Yes.
| |
46 | |
47 if (!DuplicateTokenEx(*process_token, | |
48 desired_access, | |
49 NULL, | |
50 SecurityImpersonation, | |
51 TokenPrimary, | |
52 &handle)) { | |
53 LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token"; | |
54 return false; | |
55 } | |
56 | |
57 token_out->reset(new ScopedHandle(handle)); | |
Wez
2012/02/28 00:22:07
And this would become:
token_out->Set(process_tok
alexeypa (please no reviews)
2012/02/28 01:14:04
Yes.
| |
58 return true; | |
59 } | |
60 | |
61 // Creates a copy of the current process with SE_TCB_NAME privilege enabled. | |
62 bool CreatePrivilegedToken(scoped_ptr<ScopedHandle>* token_out) { | |
Wez
2012/02/28 00:22:07
Similarly, why wrap in a scoped_ptr<>?
alexeypa (please no reviews)
2012/02/28 01:14:04
Again, to underline ownership.
| |
63 scoped_ptr<ScopedHandle> privileged_token; | |
64 DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE | | |
65 TOKEN_DUPLICATE | TOKEN_QUERY; | |
66 if (!CopyProcessToken(desired_access, &privileged_token)) { | |
67 return false; | |
68 } | |
69 | |
70 // Get the LUID for the SE_TCB_NAME privilege. | |
71 TOKEN_PRIVILEGES state; | |
72 state.PrivilegeCount = 1; | |
73 state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
74 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) { | |
75 LOG_GETLASTERROR(ERROR) << | |
76 "Failed to lookup the LUID for the SE_TCB_NAME privilege"; | |
77 return false; | |
78 } | |
79 | |
80 // Enable the SE_TCB_NAME privilege. | |
81 if (!AdjustTokenPrivileges(*privileged_token, FALSE, &state, 0, NULL, 0)) { | |
82 LOG_GETLASTERROR(ERROR) << | |
83 "Failed to enable SE_TCB_NAME privilege in a token"; | |
84 return false; | |
85 } | |
86 | |
87 *token_out = privileged_token.Pass(); | |
88 return true; | |
89 } | |
90 | |
91 // Creates a copy of the current process token for the given |session_id| so | |
92 // it can be used to launch a process in that session. | |
93 bool CreateSessionToken(uint32 session_id, | |
94 scoped_ptr<ScopedHandle>* token_out) { | |
95 | |
96 scoped_ptr<ScopedHandle> session_token; | |
97 DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | | |
98 TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY; | |
99 if (!CopyProcessToken(desired_access, &session_token)) { | |
100 return false; | |
101 } | |
102 | |
103 // Change the session ID of the token. | |
104 DWORD new_session_id = session_id; | |
105 if (!SetTokenInformation(session_token->Get(), | |
106 TokenSessionId, | |
107 &new_session_id, | |
108 sizeof(new_session_id))) { | |
109 LOG_GETLASTERROR(ERROR) << | |
110 "Failed to change session ID of a token"; | |
111 return false; | |
112 } | |
113 | |
114 *token_out = session_token.Pass(); | |
115 return true; | |
116 } | |
117 | |
118 // Launches |binary| in the security context of the user represented by | |
Wez
2012/02/28 00:22:07
nit: ... of the supplied |user_token|.
alexeypa (please no reviews)
2012/02/28 01:14:04
Done.
| |
119 // |user_token|. | |
120 bool LaunchProcessAsUser(const FilePath& binary, | |
121 const scoped_ptr<ScopedHandle>& user_token, | |
122 base::Process* process_out) { | |
Wez
2012/02/28 00:22:07
Why not return a scoped_ptr<base::Process>, rather
alexeypa (please no reviews)
2012/02/28 01:14:04
This makes the caller's code look like all other c
| |
123 string16 command_line = binary.value(); | |
124 string16 desktop = ASCIIToUTF16(kDefaultDesktopName); | |
125 | |
126 PROCESS_INFORMATION process_info; | |
127 STARTUPINFOW startup_info; | |
128 | |
129 memset(&startup_info, 0, sizeof(startup_info)); | |
130 startup_info.cb = sizeof(startup_info); | |
131 startup_info.lpDesktop = const_cast<LPWSTR>(desktop.c_str()); | |
132 | |
133 if (!CreateProcessAsUserW(*user_token, | |
134 command_line.c_str(), | |
135 const_cast<LPWSTR>(command_line.c_str()), | |
136 NULL, | |
137 NULL, | |
138 FALSE, | |
139 0, | |
140 NULL, | |
141 NULL, | |
142 &startup_info, | |
143 &process_info)) { | |
144 LOG_GETLASTERROR(ERROR) << | |
145 "Failed to launch a process with a user token"; | |
146 return false; | |
147 } | |
148 | |
149 CloseHandle(process_info.hThread); | |
150 process_out->set_handle(process_info.hProcess); | |
151 return true; | |
152 } | |
153 | |
154 } // namespace | |
155 | |
16 namespace remoting { | 156 namespace remoting { |
17 | 157 |
18 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | 158 WtsSessionProcessLauncher::WtsSessionProcessLauncher( |
19 WtsConsoleMonitor* monitor) : monitor_(monitor) { | 159 WtsConsoleMonitor* monitor, |
160 const FilePath& host_binary) | |
161 : host_binary_(host_binary), | |
162 monitor_(monitor), | |
163 state_(StateDetached) { | |
20 monitor_->AddWtsConsoleObserver(this); | 164 monitor_->AddWtsConsoleObserver(this); |
21 } | 165 } |
22 | 166 |
23 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | 167 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
168 DCHECK(state_ == StateDetached); | |
169 DCHECK(!timer_.IsRunning()); | |
170 DCHECK(process_.handle() == NULL); | |
171 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
172 | |
24 monitor_->RemoveWtsConsoleObserver(this); | 173 monitor_->RemoveWtsConsoleObserver(this); |
25 } | 174 } |
26 | 175 |
176 void WtsSessionProcessLauncher::LaunchProcess() { | |
177 DCHECK(state_ == StateStarting); | |
178 DCHECK(!timer_.IsRunning()); | |
179 DCHECK(process_.handle() == NULL); | |
180 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
181 | |
182 // Try to launch the process and attach an object watcher to the returned | |
183 // handle so that we get notified when the process terminates. | |
184 launch_time_ = base::Time::Now(); | |
185 if (LaunchProcessAsUser(host_binary_, session_token_, &process_)) { | |
186 if (process_watcher_.StartWatching(process_.handle(), this)) { | |
187 state_ = StateAttached; | |
188 return; | |
189 } else { | |
Wez
2012/02/28 00:22:07
nit: Is there anything meaningful to log in this c
alexeypa (please no reviews)
2012/02/28 01:14:04
StartWatching() will print details to the log. I t
| |
190 process_.Terminate(0); | |
191 process_.Close(); | |
192 } | |
193 } | |
194 | |
195 // Something went wrong. Try to launch the host again later. The attempts rate | |
196 // is limited by exponential backoff. | |
197 launch_backoff_ = std::max(launch_backoff_ * 2, | |
198 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
199 launch_backoff_ = std::min(launch_backoff_, | |
200 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
201 timer_.Start(FROM_HERE, launch_backoff_, | |
202 this, &WtsSessionProcessLauncher::LaunchProcess); | |
203 } | |
204 | |
205 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { | |
206 DCHECK(state_ == StateAttached); | |
207 DCHECK(!timer_.IsRunning()); | |
208 DCHECK(process_.handle() != NULL); | |
209 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
210 | |
211 // The host process has been terminated for some reason. The handle can now be | |
212 // closed. | |
213 process_.Close(); | |
214 | |
215 // Expand the backoff interval if the process has died quickly or reset it if | |
216 // it was up longer than the maximum backoff delay. | |
217 base::TimeDelta delta = base::Time::Now() - launch_time_; | |
218 if (delta < base::TimeDelta() || | |
219 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { | |
220 launch_backoff_ = base::TimeDelta(); | |
221 } else { | |
222 launch_backoff_ = std::max(launch_backoff_ * 2, | |
223 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
224 launch_backoff_ = std::min(launch_backoff_, | |
225 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
226 } | |
227 | |
228 // Try to restart the host. | |
229 state_ = StateStarting; | |
230 timer_.Start(FROM_HERE, launch_backoff_, | |
231 this, &WtsSessionProcessLauncher::LaunchProcess); | |
232 } | |
233 | |
27 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | 234 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { |
28 // TODO(alexeypa): The code injecting the host process to a session goes here. | 235 DCHECK(state_ == StateDetached); |
Wez
2012/02/28 00:22:07
This should probably be a CHECK, since OnSessionAt
alexeypa (please no reviews)
2012/02/28 01:14:04
It is actually in our control. WtsConsoleObserver'
| |
236 DCHECK(!timer_.IsRunning()); | |
237 DCHECK(process_.handle() == NULL); | |
238 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
239 | |
240 // Temporary enable the SE_TCB_NAME priviledge by impersonating a privileged | |
Wez
2012/02/28 00:22:07
typo: Temporarily
typo: privilege
Wez
2012/02/28 00:22:07
Suggest rewording: ... by impersonating a copy of
alexeypa (please no reviews)
2012/02/28 01:14:04
Done.
alexeypa (please no reviews)
2012/02/28 01:14:04
Done.
| |
241 // token. The privileged token is created as needed and kept for later reuse. | |
242 if (privileged_token_.get() == NULL) { | |
243 if (!CreatePrivilegedToken(&privileged_token_)) { | |
244 return; | |
245 } | |
246 } | |
247 | |
248 if (!ImpersonateLoggedOnUser(*privileged_token_)) { | |
249 LOG_GETLASTERROR(ERROR) << | |
250 "Failed to impersonate the privileged token"; | |
251 return; | |
252 } | |
253 | |
254 // While the SE_TCB_NAME progolege is enabled, create a session token for | |
255 // the launched process. | |
256 BOOL result = CreateSessionToken(session_id, &session_token_); | |
Wez
2012/02/28 00:22:07
nit: CreateSessionToken returns bool, not BOOL. :)
alexeypa (please no reviews)
2012/02/28 01:14:04
Indeed. :-)
| |
257 | |
258 // Revert to the default token. The default token is sufficient to call | |
259 // CreateProcessAsUser() successfully. | |
260 if (!RevertToSelf()) { | |
Wez
2012/02/28 00:22:07
The process should crash if this fails.
alexeypa (please no reviews)
2012/02/28 01:14:04
Done.
| |
261 LOG_GETLASTERROR(ERROR) << | |
262 "Failed to revert to the default process token"; | |
263 } | |
264 | |
265 if (!result) | |
266 return; | |
267 | |
268 // Now try to launch the host. | |
269 state_ = StateStarting; | |
270 LaunchProcess(); | |
29 } | 271 } |
30 | 272 |
31 void WtsSessionProcessLauncher::OnSessionDetached() { | 273 void WtsSessionProcessLauncher::OnSessionDetached() { |
32 // TODO(alexeypa): The code terminating the host process goes here. | 274 DCHECK(state_ == StateDetached || |
275 state_ == StateStarting || | |
276 state_ == StateAttached); | |
277 | |
278 switch (state_) { | |
279 case StateDetached: | |
Wez
2012/02/28 00:22:07
Why does OnSessionDetached cope with being called
alexeypa (please no reviews)
2012/02/28 01:14:04
Because the caller makes sure that ordering in rig
| |
280 DCHECK(!timer_.IsRunning()); | |
281 DCHECK(process_.handle() == NULL); | |
282 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
283 break; | |
284 | |
285 case StateStarting: | |
286 DCHECK(timer_.IsRunning()); | |
287 DCHECK(process_.handle() == NULL); | |
288 DCHECK(process_watcher_.GetWatchedObject() == NULL); | |
289 | |
290 timer_.Stop(); | |
291 launch_backoff_ = base::TimeDelta(); | |
292 state_ = StateDetached; | |
293 break; | |
294 | |
295 case StateAttached: | |
296 DCHECK(!timer_.IsRunning()); | |
297 DCHECK(process_.handle() != NULL); | |
298 DCHECK(process_watcher_.GetWatchedObject() != NULL); | |
299 | |
300 process_watcher_.StopWatching(); | |
301 process_.Terminate(0); | |
302 process_.Close(); | |
303 state_ = StateDetached; | |
304 break; | |
305 } | |
33 } | 306 } |
34 | 307 |
35 } // namespace remoting | 308 } // namespace remoting |
OLD | NEW |