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)) { |
| 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); |
| 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 |