Home | History | Annotate | Download | only in win
      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