Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Side by Side Diff: remoting/host/wts_session_process_launcher_win.cc

Issue 9476017: Chromoting service now launches the host process in the session the is currently attached to the ph… (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698