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 #include "remoting/host/win/launch_process_with_token.h" 6 7 #include <windows.h> 8 #include <winternl.h> 9 10 #include <limits> 11 12 #include "base/logging.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/rand_util.h" 15 #include "base/scoped_native_library.h" 16 #include "base/strings/string16.h" 17 #include "base/strings/stringprintf.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/win/scoped_handle.h" 20 #include "base/win/scoped_process_information.h" 21 #include "base/win/windows_version.h" 22 23 using base::win::ScopedHandle; 24 25 namespace { 26 27 const char kCreateProcessDefaultPipeNameFormat[] = 28 "\\\\.\\Pipe\\TerminalServer\\SystemExecSrvr\\%d"; 29 30 // Undocumented WINSTATIONINFOCLASS value causing 31 // winsta!WinStationQueryInformationW() to return the name of the pipe for 32 // requesting cross-session process creation. 33 const WINSTATIONINFOCLASS kCreateProcessPipeNameClass = 34 static_cast<WINSTATIONINFOCLASS>(0x21); 35 36 const int kPipeBusyWaitTimeoutMs = 2000; 37 const int kPipeConnectMaxAttempts = 3; 38 39 // Terminates the process and closes process and thread handles in 40 // |process_information| structure. 41 void CloseHandlesAndTerminateProcess(PROCESS_INFORMATION* process_information) { 42 if (process_information->hThread) { 43 CloseHandle(process_information->hThread); 44 process_information->hThread = NULL; 45 } 46 47 if (process_information->hProcess) { 48 TerminateProcess(process_information->hProcess, CONTROL_C_EXIT); 49 CloseHandle(process_information->hProcess); 50 process_information->hProcess = NULL; 51 } 52 } 53 54 // Connects to the executor server corresponding to |session_id|. 55 bool ConnectToExecutionServer(uint32 session_id, 56 base::win::ScopedHandle* pipe_out) { 57 string16 pipe_name; 58 59 // Use winsta!WinStationQueryInformationW() to determine the process creation 60 // pipe name for the session. 61 base::FilePath winsta_path(base::GetNativeLibraryName(UTF8ToUTF16("winsta"))); 62 base::ScopedNativeLibrary winsta(winsta_path); 63 if (winsta.is_valid()) { 64 PWINSTATIONQUERYINFORMATIONW win_station_query_information = 65 static_cast<PWINSTATIONQUERYINFORMATIONW>( 66 winsta.GetFunctionPointer("WinStationQueryInformationW")); 67 if (win_station_query_information) { 68 wchar_t name[MAX_PATH]; 69 ULONG name_length; 70 if (win_station_query_information(0, 71 session_id, 72 kCreateProcessPipeNameClass, 73 name, 74 sizeof(name), 75 &name_length)) { 76 pipe_name.assign(name); 77 } 78 } 79 } 80 81 // Use the default pipe name if we couldn't query its name. 82 if (pipe_name.empty()) { 83 pipe_name = UTF8ToUTF16( 84 base::StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id)); 85 } 86 87 // Try to connect to the named pipe. 88 base::win::ScopedHandle pipe; 89 for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { 90 pipe.Set(CreateFile(pipe_name.c_str(), 91 GENERIC_READ | GENERIC_WRITE, 92 0, 93 NULL, 94 OPEN_EXISTING, 95 0, 96 NULL)); 97 if (pipe.IsValid()) { 98 break; 99 } 100 101 // Cannot continue retrying if error is something other than 102 // ERROR_PIPE_BUSY. 103 if (GetLastError() != ERROR_PIPE_BUSY) { 104 break; 105 } 106 107 // Cannot continue retrying if wait on pipe fails. 108 if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) { 109 break; 110 } 111 } 112 113 if (!pipe.IsValid()) { 114 LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'"; 115 return false; 116 } 117 118 *pipe_out = pipe.Pass(); 119 return true; 120 } 121 122 // Copies the process token making it a primary impersonation token. 123 // The returned handle will have |desired_access| rights. 124 bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) { 125 ScopedHandle process_token; 126 if (!OpenProcessToken(GetCurrentProcess(), 127 TOKEN_DUPLICATE | desired_access, 128 process_token.Receive())) { 129 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; 130 return false; 131 } 132 133 ScopedHandle copied_token; 134 if (!DuplicateTokenEx(process_token, 135 desired_access, 136 NULL, 137 SecurityImpersonation, 138 TokenPrimary, 139 copied_token.Receive())) { 140 LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token"; 141 return false; 142 } 143 144 *token_out = copied_token.Pass(); 145 return true; 146 } 147 148 // Creates a copy of the current process with SE_TCB_NAME privilege enabled. 149 bool CreatePrivilegedToken(ScopedHandle* token_out) { 150 ScopedHandle privileged_token; 151 DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE | 152 TOKEN_DUPLICATE | TOKEN_QUERY; 153 if (!CopyProcessToken(desired_access, &privileged_token)) { 154 return false; 155 } 156 157 // Get the LUID for the SE_TCB_NAME privilege. 158 TOKEN_PRIVILEGES state; 159 state.PrivilegeCount = 1; 160 state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 161 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) { 162 LOG_GETLASTERROR(ERROR) << 163 "Failed to lookup the LUID for the SE_TCB_NAME privilege"; 164 return false; 165 } 166 167 // Enable the SE_TCB_NAME privilege. 168 if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) { 169 LOG_GETLASTERROR(ERROR) << 170 "Failed to enable SE_TCB_NAME privilege in a token"; 171 return false; 172 } 173 174 *token_out = privileged_token.Pass(); 175 return true; 176 } 177 178 // Fills the process and thread handles in the passed |process_information| 179 // structure and resume the process if the caller didn't want to suspend it. 180 bool ProcessCreateProcessResponse(DWORD creation_flags, 181 PROCESS_INFORMATION* process_information) { 182 // The execution server does not return handles to the created process and 183 // thread. 184 if (!process_information->hProcess) { 185 // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of 186 // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from 187 // the XP version of the SDK. 188 DWORD desired_access = 189 STANDARD_RIGHTS_REQUIRED | 190 SYNCHRONIZE | 191 PROCESS_TERMINATE | 192 PROCESS_CREATE_THREAD | 193 PROCESS_SET_SESSIONID | 194 PROCESS_VM_OPERATION | 195 PROCESS_VM_READ | 196 PROCESS_VM_WRITE | 197 PROCESS_DUP_HANDLE | 198 PROCESS_CREATE_PROCESS | 199 PROCESS_SET_QUOTA | 200 PROCESS_SET_INFORMATION | 201 PROCESS_QUERY_INFORMATION | 202 PROCESS_SUSPEND_RESUME; 203 process_information->hProcess = 204 OpenProcess(desired_access, 205 FALSE, 206 process_information->dwProcessId); 207 if (!process_information->hProcess) { 208 LOG_GETLASTERROR(ERROR) << "Failed to open the process " 209 << process_information->dwProcessId; 210 return false; 211 } 212 } 213 214 if (!process_information->hThread) { 215 // N.B. THREAD_ALL_ACCESS is different in XP and Vista+ versions of 216 // the SDK. |desired_access| below is effectively THREAD_ALL_ACCESS from 217 // the XP version of the SDK. 218 DWORD desired_access = 219 STANDARD_RIGHTS_REQUIRED | 220 SYNCHRONIZE | 221 THREAD_TERMINATE | 222 THREAD_SUSPEND_RESUME | 223 THREAD_GET_CONTEXT | 224 THREAD_SET_CONTEXT | 225 THREAD_QUERY_INFORMATION | 226 THREAD_SET_INFORMATION | 227 THREAD_SET_THREAD_TOKEN | 228 THREAD_IMPERSONATE | 229 THREAD_DIRECT_IMPERSONATION; 230 process_information->hThread = 231 OpenThread(desired_access, 232 FALSE, 233 process_information->dwThreadId); 234 if (!process_information->hThread) { 235 LOG_GETLASTERROR(ERROR) << "Failed to open the thread " 236 << process_information->dwThreadId; 237 return false; 238 } 239 } 240 241 // Resume the thread if the caller didn't want to suspend the process. 242 if ((creation_flags & CREATE_SUSPENDED) == 0) { 243 if (!ResumeThread(process_information->hThread)) { 244 LOG_GETLASTERROR(ERROR) << "Failed to resume the thread " 245 << process_information->dwThreadId; 246 return false; 247 } 248 } 249 250 return true; 251 } 252 253 // Receives the response to a remote process create request. 254 bool ReceiveCreateProcessResponse( 255 HANDLE pipe, 256 PROCESS_INFORMATION* process_information_out) { 257 struct CreateProcessResponse { 258 DWORD size; 259 BOOL success; 260 DWORD last_error; 261 PROCESS_INFORMATION process_information; 262 }; 263 264 DWORD bytes; 265 CreateProcessResponse response; 266 if (!ReadFile(pipe, &response, sizeof(response), &bytes, NULL)) { 267 LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response"; 268 return false; 269 } 270 271 // The server sends the data in one chunk so if we didn't received a complete 272 // answer something bad happend and there is no point in retrying. 273 if (bytes != sizeof(response)) { 274 SetLastError(ERROR_RECEIVE_PARTIAL); 275 return false; 276 } 277 278 if (!response.success) { 279 SetLastError(response.last_error); 280 return false; 281 } 282 283 *process_information_out = response.process_information; 284 return true; 285 } 286 287 // Sends a remote process create request to the execution server. 288 bool SendCreateProcessRequest( 289 HANDLE pipe, 290 const base::FilePath::StringType& application_name, 291 const CommandLine::StringType& command_line, 292 DWORD creation_flags, 293 const char16* desktop_name) { 294 // |CreateProcessRequest| structure passes the same parameters to 295 // the execution server as CreateProcessAsUser() function does. Strings are 296 // stored as wide strings immediately after the structure. String pointers are 297 // represented as byte offsets to string data from the beginning of 298 // the structure. 299 struct CreateProcessRequest { 300 DWORD size; 301 DWORD process_id; 302 BOOL use_default_token; 303 HANDLE token; 304 LPWSTR application_name; 305 LPWSTR command_line; 306 SECURITY_ATTRIBUTES process_attributes; 307 SECURITY_ATTRIBUTES thread_attributes; 308 BOOL inherit_handles; 309 DWORD creation_flags; 310 LPVOID environment; 311 LPWSTR current_directory; 312 STARTUPINFOW startup_info; 313 PROCESS_INFORMATION process_information; 314 }; 315 316 string16 desktop; 317 if (desktop_name) 318 desktop = desktop_name; 319 320 // Allocate a large enough buffer to hold the CreateProcessRequest structure 321 // and three NULL-terminated string parameters. 322 size_t size = sizeof(CreateProcessRequest) + sizeof(wchar_t) * 323 (application_name.size() + command_line.size() + desktop.size() + 3); 324 scoped_ptr<char[]> buffer(new char[size]); 325 memset(buffer.get(), 0, size); 326 327 // Marshal the input parameters. 328 CreateProcessRequest* request = 329 reinterpret_cast<CreateProcessRequest*>(buffer.get()); 330 request->size = size; 331 request->process_id = GetCurrentProcessId(); 332 request->use_default_token = TRUE; 333 // Always pass CREATE_SUSPENDED to avoid a race between the created process 334 // exiting too soon and OpenProcess() call below. 335 request->creation_flags = creation_flags | CREATE_SUSPENDED; 336 request->startup_info.cb = sizeof(request->startup_info); 337 338 size_t buffer_offset = sizeof(CreateProcessRequest); 339 340 request->application_name = reinterpret_cast<LPWSTR>(buffer_offset); 341 std::copy(application_name.begin(), 342 application_name.end(), 343 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); 344 buffer_offset += (application_name.size() + 1) * sizeof(wchar_t); 345 346 request->command_line = reinterpret_cast<LPWSTR>(buffer_offset); 347 std::copy(command_line.begin(), 348 command_line.end(), 349 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); 350 buffer_offset += (command_line.size() + 1) * sizeof(wchar_t); 351 352 request->startup_info.lpDesktop = 353 reinterpret_cast<LPWSTR>(buffer_offset); 354 std::copy(desktop.begin(), 355 desktop.end(), 356 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); 357 358 // Pass the request to create a process in the target session. 359 DWORD bytes; 360 if (!WriteFile(pipe, buffer.get(), size, &bytes, NULL)) { 361 LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request"; 362 return false; 363 } 364 365 return true; 366 } 367 368 // Requests the execution server to create a process in the specified session 369 // using the default (i.e. Winlogon) token. This routine relies on undocumented 370 // OS functionality and will likely not work on anything but XP or W2K3. 371 bool CreateRemoteSessionProcess( 372 uint32 session_id, 373 const base::FilePath::StringType& application_name, 374 const CommandLine::StringType& command_line, 375 DWORD creation_flags, 376 const char16* desktop_name, 377 PROCESS_INFORMATION* process_information_out) 378 { 379 DCHECK_LT(base::win::GetVersion(), base::win::VERSION_VISTA); 380 381 base::win::ScopedHandle pipe; 382 if (!ConnectToExecutionServer(session_id, &pipe)) 383 return false; 384 385 if (!SendCreateProcessRequest(pipe, application_name, command_line, 386 creation_flags, desktop_name)) { 387 return false; 388 } 389 390 PROCESS_INFORMATION process_information; 391 if (!ReceiveCreateProcessResponse(pipe, &process_information)) 392 return false; 393 394 if (!ProcessCreateProcessResponse(creation_flags, &process_information)) { 395 CloseHandlesAndTerminateProcess(&process_information); 396 return false; 397 } 398 399 *process_information_out = process_information; 400 return true; 401 } 402 403 } // namespace 404 405 namespace remoting { 406 407 base::LazyInstance<base::Lock>::Leaky g_inherit_handles_lock = 408 LAZY_INSTANCE_INITIALIZER; 409 410 // Creates a copy of the current process token for the given |session_id| so 411 // it can be used to launch a process in that session. 412 bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) { 413 ScopedHandle session_token; 414 DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | 415 TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY; 416 if (!CopyProcessToken(desired_access, &session_token)) { 417 return false; 418 } 419 420 // Temporarily enable the SE_TCB_NAME privilege as it is required by 421 // SetTokenInformation(TokenSessionId). 422 ScopedHandle privileged_token; 423 if (!CreatePrivilegedToken(&privileged_token)) { 424 return false; 425 } 426 if (!ImpersonateLoggedOnUser(privileged_token)) { 427 LOG_GETLASTERROR(ERROR) << 428 "Failed to impersonate the privileged token"; 429 return false; 430 } 431 432 // Change the session ID of the token. 433 DWORD new_session_id = session_id; 434 if (!SetTokenInformation(session_token, 435 TokenSessionId, 436 &new_session_id, 437 sizeof(new_session_id))) { 438 LOG_GETLASTERROR(ERROR) << "Failed to change session ID of a token"; 439 440 // Revert to the default token. 441 CHECK(RevertToSelf()); 442 return false; 443 } 444 445 // Revert to the default token. 446 CHECK(RevertToSelf()); 447 448 *token_out = session_token.Pass(); 449 return true; 450 } 451 452 bool LaunchProcessWithToken(const base::FilePath& binary, 453 const CommandLine::StringType& command_line, 454 HANDLE user_token, 455 SECURITY_ATTRIBUTES* process_attributes, 456 SECURITY_ATTRIBUTES* thread_attributes, 457 bool inherit_handles, 458 DWORD creation_flags, 459 const char16* desktop_name, 460 ScopedHandle* process_out, 461 ScopedHandle* thread_out) { 462 base::FilePath::StringType application_name = binary.value(); 463 464 STARTUPINFOW startup_info; 465 memset(&startup_info, 0, sizeof(startup_info)); 466 startup_info.cb = sizeof(startup_info); 467 if (desktop_name) 468 startup_info.lpDesktop = const_cast<char16*>(desktop_name); 469 470 base::win::ScopedProcessInformation process_info; 471 BOOL result = CreateProcessAsUser(user_token, 472 application_name.c_str(), 473 const_cast<LPWSTR>(command_line.c_str()), 474 process_attributes, 475 thread_attributes, 476 inherit_handles, 477 creation_flags, 478 NULL, 479 NULL, 480 &startup_info, 481 process_info.Receive()); 482 483 // CreateProcessAsUser will fail on XP and W2K3 with ERROR_PIPE_NOT_CONNECTED 484 // if the user hasn't logged to the target session yet. In such a case 485 // we try to talk to the execution server directly emulating what 486 // the undocumented and not-exported advapi32!CreateRemoteSessionProcessW() 487 // function does. The created process will run under Winlogon'a token instead 488 // of |user_token|. Since Winlogon runs as SYSTEM, this suits our needs. 489 if (!result && 490 GetLastError() == ERROR_PIPE_NOT_CONNECTED && 491 base::win::GetVersion() < base::win::VERSION_VISTA) { 492 DWORD session_id; 493 DWORD return_length; 494 result = GetTokenInformation(user_token, 495 TokenSessionId, 496 &session_id, 497 sizeof(session_id), 498 &return_length); 499 if (result && session_id != 0) { 500 result = CreateRemoteSessionProcess(session_id, 501 application_name, 502 command_line, 503 creation_flags, 504 desktop_name, 505 process_info.Receive()); 506 } else { 507 // Restore the error status returned by CreateProcessAsUser(). 508 result = FALSE; 509 SetLastError(ERROR_PIPE_NOT_CONNECTED); 510 } 511 } 512 513 if (!result) { 514 LOG_GETLASTERROR(ERROR) << 515 "Failed to launch a process with a user token"; 516 return false; 517 } 518 519 CHECK(process_info.IsValid()); 520 process_out->Set(process_info.TakeProcessHandle()); 521 thread_out->Set(process_info.TakeThreadHandle()); 522 return true; 523 } 524 525 } // namespace remoting 526