Index: remoting/host/launch_process_in_session_win.cc |
=================================================================== |
--- remoting/host/launch_process_in_session_win.cc (revision 149037) |
+++ remoting/host/launch_process_in_session_win.cc (working copy) |
@@ -1,312 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "remoting/host/launch_process_in_session_win.h" |
- |
-#include <windows.h> |
-#include <winternl.h> |
- |
-#include "base/logging.h" |
-#include "base/memory/scoped_ptr.h" |
-#include "base/scoped_native_library.h" |
-#include "base/stringprintf.h" |
-#include "base/utf_string_conversions.h" |
-#include "base/win/scoped_handle.h" |
-#include "base/win/scoped_process_information.h" |
-#include "base/win/windows_version.h" |
- |
-using base::win::ScopedHandle; |
- |
-namespace { |
- |
-const wchar_t kCreateProcessDefaultPipeNameFormat[] = |
- L"\\\\.\\Pipe\\TerminalServer\\SystemExecSrvr\\%d"; |
- |
-// Undocumented WINSTATIONINFOCLASS value causing |
-// winsta!WinStationQueryInformationW() to return the name of the pipe for |
-// requesting cross-session process creation. |
-const WINSTATIONINFOCLASS kCreateProcessPipeNameClass = |
- static_cast<WINSTATIONINFOCLASS>(0x21); |
- |
-const int kPipeBusyWaitTimeoutMs = 2000; |
-const int kPipeConnectMaxAttempts = 3; |
- |
-// The minimum and maximum delays between attempts to inject host process into |
-// a session. |
-const int kMaxLaunchDelaySeconds = 60; |
-const int kMinLaunchDelaySeconds = 1; |
- |
-// Name of the default session desktop. |
-wchar_t kDefaultDesktopName[] = L"winsta0\\default"; |
- |
-// Requests the execution server to create a process in the specified session |
-// using the default (i.e. Winlogon) token. This routine relies on undocumented |
-// OS functionality and will likely not work on anything but XP or W2K3. |
-bool CreateRemoteSessionProcess( |
- uint32 session_id, |
- const std::wstring& application_name, |
- const std::wstring& command_line, |
- PROCESS_INFORMATION* process_information_out) |
-{ |
- DCHECK(base::win::GetVersion() == base::win::VERSION_XP); |
- |
- std::wstring pipe_name; |
- |
- // Use winsta!WinStationQueryInformationW() to determine the process creation |
- // pipe name for the session. |
- FilePath winsta_path(base::GetNativeLibraryName(UTF8ToUTF16("winsta"))); |
- base::ScopedNativeLibrary winsta(winsta_path); |
- if (winsta.is_valid()) { |
- PWINSTATIONQUERYINFORMATIONW win_station_query_information = |
- static_cast<PWINSTATIONQUERYINFORMATIONW>( |
- winsta.GetFunctionPointer("WinStationQueryInformationW")); |
- if (win_station_query_information) { |
- wchar_t name[MAX_PATH]; |
- ULONG name_length; |
- if (win_station_query_information(0, |
- session_id, |
- kCreateProcessPipeNameClass, |
- name, |
- sizeof(name), |
- &name_length)) { |
- pipe_name.assign(name); |
- } |
- } |
- } |
- |
- // Use the default pipe name if we couldn't query its name. |
- if (pipe_name.empty()) { |
- pipe_name = StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id); |
- } |
- |
- // Try to connect to the named pipe. |
- base::win::ScopedHandle pipe; |
- for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { |
- pipe.Set(CreateFile(pipe_name.c_str(), |
- GENERIC_READ | GENERIC_WRITE, |
- 0, |
- NULL, |
- OPEN_EXISTING, |
- 0, |
- NULL)); |
- if (pipe.IsValid()) { |
- break; |
- } |
- |
- // Cannot continue retrying if error is something other than |
- // ERROR_PIPE_BUSY. |
- if (GetLastError() != ERROR_PIPE_BUSY) { |
- break; |
- } |
- |
- // Cannot continue retrying if wait on pipe fails. |
- if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) { |
- break; |
- } |
- } |
- |
- if (!pipe.IsValid()) { |
- LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'"; |
- return false; |
- } |
- |
- std::wstring desktop_name(kDefaultDesktopName); |
- |
- // |CreateProcessRequest| structure passes the same parameters to |
- // the execution server as CreateProcessAsUser() function does. Strings are |
- // stored as wide strings immediately after the structure. String pointers are |
- // represented as byte offsets to string data from the beginning of |
- // the structure. |
- struct CreateProcessRequest { |
- DWORD size; |
- DWORD process_id; |
- BOOL use_default_token; |
- HANDLE token; |
- LPWSTR application_name; |
- LPWSTR command_line; |
- SECURITY_ATTRIBUTES process_attributes; |
- SECURITY_ATTRIBUTES thread_attributes; |
- BOOL inherit_handles; |
- DWORD creation_flags; |
- LPVOID environment; |
- LPWSTR current_directory; |
- STARTUPINFOW startup_info; |
- PROCESS_INFORMATION process_information; |
- }; |
- |
- // Allocate a large enough buffer to hold the CreateProcessRequest structure |
- // and three NULL-terminated string parameters. |
- size_t size = sizeof(CreateProcessRequest) + sizeof(wchar_t) * |
- (application_name.size() + command_line.size() + desktop_name.size() + 3); |
- scoped_array<char> buffer(new char[size]); |
- memset(buffer.get(), 0, size); |
- |
- // Marshal the input parameters. |
- CreateProcessRequest* request = |
- reinterpret_cast<CreateProcessRequest*>(buffer.get()); |
- request->size = size; |
- request->use_default_token = TRUE; |
- request->process_id = GetCurrentProcessId(); |
- request->startup_info.cb = sizeof(request->startup_info); |
- |
- size_t buffer_offset = sizeof(CreateProcessRequest); |
- |
- request->application_name = reinterpret_cast<LPWSTR>(buffer_offset); |
- std::copy(application_name.begin(), |
- application_name.end(), |
- reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); |
- buffer_offset += (application_name.size() + 1) * sizeof(wchar_t); |
- |
- request->command_line = reinterpret_cast<LPWSTR>(buffer_offset); |
- std::copy(command_line.begin(), |
- command_line.end(), |
- reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); |
- buffer_offset += (command_line.size() + 1) * sizeof(wchar_t); |
- |
- request->startup_info.lpDesktop = |
- reinterpret_cast<LPWSTR>(buffer_offset); |
- std::copy(desktop_name.begin(), |
- desktop_name.end(), |
- reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); |
- |
- // Pass the request to create a process in the target session. |
- DWORD bytes; |
- if (!WriteFile(pipe.Get(), buffer.get(), size, &bytes, NULL)) { |
- LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request"; |
- return false; |
- } |
- |
- // Receive the response. |
- struct CreateProcessResponse { |
- DWORD size; |
- BOOL success; |
- DWORD last_error; |
- PROCESS_INFORMATION process_information; |
- }; |
- |
- CreateProcessResponse response; |
- if (!ReadFile(pipe.Get(), &response, sizeof(response), &bytes, NULL)) { |
- LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response"; |
- return false; |
- } |
- |
- // The server sends the data in one chunk so if we didn't received a complete |
- // answer something bad happend and there is no point in retrying. |
- if (bytes != sizeof(response)) { |
- SetLastError(ERROR_RECEIVE_PARTIAL); |
- return false; |
- } |
- |
- if (!response.success) { |
- SetLastError(response.last_error); |
- return false; |
- } |
- |
- // The execution server does not return handles to the created process and |
- // thread. |
- if (response.process_information.hProcess == NULL) { |
- // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of |
- // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from |
- // the XP version of the SDK. |
- DWORD desired_access = |
- STANDARD_RIGHTS_REQUIRED | |
- SYNCHRONIZE | |
- PROCESS_TERMINATE | |
- PROCESS_CREATE_THREAD | |
- PROCESS_SET_SESSIONID | |
- PROCESS_VM_OPERATION | |
- PROCESS_VM_READ | |
- PROCESS_VM_WRITE | |
- PROCESS_DUP_HANDLE | |
- PROCESS_CREATE_PROCESS | |
- PROCESS_SET_QUOTA | |
- PROCESS_SET_INFORMATION | |
- PROCESS_QUERY_INFORMATION | |
- PROCESS_SUSPEND_RESUME; |
- response.process_information.hProcess = |
- OpenProcess(desired_access, |
- FALSE, |
- response.process_information.dwProcessId); |
- if (!response.process_information.hProcess) { |
- LOG_GETLASTERROR(ERROR) << "Failed to open process " |
- << response.process_information.dwProcessId; |
- return false; |
- } |
- } |
- |
- *process_information_out = response.process_information; |
- return true; |
-} |
- |
-} // namespace |
- |
-namespace remoting { |
- |
-// Launches |binary| in a different session. The target session is specified by |
-// |user_token|. |
-bool LaunchProcessInSession(const FilePath& binary, |
- const std::wstring& command_line, |
- HANDLE user_token, |
- base::Process* process_out) { |
- std::wstring application_name = binary.value(); |
- |
- base::win::ScopedProcessInformation process_info; |
- STARTUPINFOW startup_info; |
- |
- memset(&startup_info, 0, sizeof(startup_info)); |
- startup_info.cb = sizeof(startup_info); |
- startup_info.lpDesktop = kDefaultDesktopName; |
- |
- BOOL result = CreateProcessAsUser(user_token, |
- application_name.c_str(), |
- const_cast<LPWSTR>(command_line.c_str()), |
- NULL, |
- NULL, |
- FALSE, |
- 0, |
- NULL, |
- NULL, |
- &startup_info, |
- process_info.Receive()); |
- |
- // CreateProcessAsUser will fail on XP and W2K3 with ERROR_PIPE_NOT_CONNECTED |
- // if the user hasn't logged to the target session yet. In such a case |
- // we try to talk to the execution server directly emulating what |
- // the undocumented and not-exported advapi32!CreateRemoteSessionProcessW() |
- // function does. The created process will run under Winlogon'a token instead |
- // of |user_token|. Since Winlogon runs as SYSTEM, this suits our needs. |
- if (!result && |
- GetLastError() == ERROR_PIPE_NOT_CONNECTED && |
- base::win::GetVersion() == base::win::VERSION_XP) { |
- DWORD session_id; |
- DWORD return_length; |
- result = GetTokenInformation(user_token, |
- TokenSessionId, |
- &session_id, |
- sizeof(session_id), |
- &return_length); |
- if (result && session_id != 0) { |
- result = CreateRemoteSessionProcess(session_id, |
- application_name, |
- command_line, |
- process_info.Receive()); |
- } else { |
- // Restore the error status returned by CreateProcessAsUser(). |
- result = FALSE; |
- SetLastError(ERROR_PIPE_NOT_CONNECTED); |
- } |
- } |
- |
- if (!result) { |
- LOG_GETLASTERROR(ERROR) << |
- "Failed to launch a process with a user token"; |
- return false; |
- } |
- |
- CHECK(process_info.IsValid()); |
- process_out->set_handle(process_info.TakeProcessHandle()); |
- return true; |
-} |
- |
-} // namespace remoting |