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   base::string16 pipe_name;
     58 
     59   // Use winsta!WinStationQueryInformationW() to determine the process creation
     60   // pipe name for the session.
     61   base::FilePath winsta_path(
     62       base::GetNativeLibraryName(base::UTF8ToUTF16("winsta")));
     63   base::ScopedNativeLibrary winsta(winsta_path);
     64   if (winsta.is_valid()) {
     65     PWINSTATIONQUERYINFORMATIONW win_station_query_information =
     66         reinterpret_cast<PWINSTATIONQUERYINFORMATIONW>(
     67             winsta.GetFunctionPointer("WinStationQueryInformationW"));
     68     if (win_station_query_information) {
     69       wchar_t name[MAX_PATH];
     70       ULONG name_length;
     71       if (win_station_query_information(0,
     72                                         session_id,
     73                                         kCreateProcessPipeNameClass,
     74                                         name,
     75                                         sizeof(name),
     76                                         &name_length)) {
     77         pipe_name.assign(name);
     78       }
     79     }
     80   }
     81 
     82   // Use the default pipe name if we couldn't query its name.
     83   if (pipe_name.empty()) {
     84     pipe_name = base::UTF8ToUTF16(
     85         base::StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id));
     86   }
     87 
     88   // Try to connect to the named pipe.
     89   base::win::ScopedHandle pipe;
     90   for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
     91     pipe.Set(CreateFile(pipe_name.c_str(),
     92                         GENERIC_READ | GENERIC_WRITE,
     93                         0,
     94                         NULL,
     95                         OPEN_EXISTING,
     96                         0,
     97                         NULL));
     98     if (pipe.IsValid()) {
     99       break;
    100     }
    101 
    102     // Cannot continue retrying if error is something other than
    103     // ERROR_PIPE_BUSY.
    104     if (GetLastError() != ERROR_PIPE_BUSY) {
    105       break;
    106     }
    107 
    108     // Cannot continue retrying if wait on pipe fails.
    109     if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) {
    110       break;
    111     }
    112   }
    113 
    114   if (!pipe.IsValid()) {
    115     PLOG(ERROR) << "Failed to connect to '" << pipe_name << "'";
    116     return false;
    117   }
    118 
    119   *pipe_out = pipe.Pass();
    120   return true;
    121 }
    122 
    123 // Copies the process token making it a primary impersonation token.
    124 // The returned handle will have |desired_access| rights.
    125 bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) {
    126   HANDLE temp_handle;
    127   if (!OpenProcessToken(GetCurrentProcess(),
    128                         TOKEN_DUPLICATE | desired_access,
    129                         &temp_handle)) {
    130     PLOG(ERROR) << "Failed to open process token";
    131     return false;
    132   }
    133   ScopedHandle process_token(temp_handle);
    134 
    135   if (!DuplicateTokenEx(process_token.Get(),
    136                         desired_access,
    137                         NULL,
    138                         SecurityImpersonation,
    139                         TokenPrimary,
    140                         &temp_handle)) {
    141     PLOG(ERROR) << "Failed to duplicate the process token";
    142     return false;
    143   }
    144 
    145   token_out->Set(temp_handle);
    146   return true;
    147 }
    148 
    149 // Creates a copy of the current process with SE_TCB_NAME privilege enabled.
    150 bool CreatePrivilegedToken(ScopedHandle* token_out) {
    151   ScopedHandle privileged_token;
    152   DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE |
    153                          TOKEN_DUPLICATE | TOKEN_QUERY;
    154   if (!CopyProcessToken(desired_access, &privileged_token)) {
    155     return false;
    156   }
    157 
    158   // Get the LUID for the SE_TCB_NAME privilege.
    159   TOKEN_PRIVILEGES state;
    160   state.PrivilegeCount = 1;
    161   state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    162   if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) {
    163     PLOG(ERROR) << "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.Get(), FALSE, &state, 0, NULL,
    169                              0)) {
    170     PLOG(ERROR) << "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       PLOG(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       PLOG(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       PLOG(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     PLOG(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 base::CommandLine::StringType& command_line,
    292     DWORD creation_flags,
    293     const base::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   base::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     PLOG(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 base::CommandLine::StringType& command_line,
    375     DWORD creation_flags,
    376     const base::char16* desktop_name,
    377     PROCESS_INFORMATION* process_information_out) {
    378   DCHECK_LT(base::win::GetVersion(), base::win::VERSION_VISTA);
    379 
    380   base::win::ScopedHandle pipe;
    381   if (!ConnectToExecutionServer(session_id, &pipe))
    382     return false;
    383 
    384   if (!SendCreateProcessRequest(pipe.Get(), application_name, command_line,
    385                                 creation_flags, desktop_name)) {
    386     return false;
    387   }
    388 
    389   PROCESS_INFORMATION process_information;
    390   if (!ReceiveCreateProcessResponse(pipe.Get(), &process_information))
    391     return false;
    392 
    393   if (!ProcessCreateProcessResponse(creation_flags, &process_information)) {
    394     CloseHandlesAndTerminateProcess(&process_information);
    395     return false;
    396   }
    397 
    398   *process_information_out = process_information;
    399   return true;
    400 }
    401 
    402 }  // namespace
    403 
    404 namespace remoting {
    405 
    406 base::LazyInstance<base::Lock>::Leaky g_inherit_handles_lock =
    407     LAZY_INSTANCE_INITIALIZER;
    408 
    409 // Creates a copy of the current process token for the given |session_id| so
    410 // it can be used to launch a process in that session.
    411 bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
    412   ScopedHandle session_token;
    413   DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID |
    414                          TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY;
    415   if (!CopyProcessToken(desired_access, &session_token)) {
    416     return false;
    417   }
    418 
    419   // Temporarily enable the SE_TCB_NAME privilege as it is required by
    420   // SetTokenInformation(TokenSessionId).
    421   ScopedHandle privileged_token;
    422   if (!CreatePrivilegedToken(&privileged_token)) {
    423     return false;
    424   }
    425   if (!ImpersonateLoggedOnUser(privileged_token.Get())) {
    426     PLOG(ERROR) << "Failed to impersonate the privileged token";
    427     return false;
    428   }
    429 
    430   // Change the session ID of the token.
    431   DWORD new_session_id = session_id;
    432   if (!SetTokenInformation(session_token.Get(),
    433                            TokenSessionId,
    434                            &new_session_id,
    435                            sizeof(new_session_id))) {
    436     PLOG(ERROR) << "Failed to change session ID of a token";
    437 
    438     // Revert to the default token.
    439     CHECK(RevertToSelf());
    440     return false;
    441   }
    442 
    443   // Revert to the default token.
    444   CHECK(RevertToSelf());
    445 
    446   *token_out = session_token.Pass();
    447   return true;
    448 }
    449 
    450 bool LaunchProcessWithToken(const base::FilePath& binary,
    451                             const base::CommandLine::StringType& command_line,
    452                             HANDLE user_token,
    453                             SECURITY_ATTRIBUTES* process_attributes,
    454                             SECURITY_ATTRIBUTES* thread_attributes,
    455                             bool inherit_handles,
    456                             DWORD creation_flags,
    457                             const base::char16* desktop_name,
    458                             ScopedHandle* process_out,
    459                             ScopedHandle* thread_out) {
    460   base::FilePath::StringType application_name = binary.value();
    461 
    462   STARTUPINFOW startup_info;
    463   memset(&startup_info, 0, sizeof(startup_info));
    464   startup_info.cb = sizeof(startup_info);
    465   if (desktop_name)
    466     startup_info.lpDesktop = const_cast<base::char16*>(desktop_name);
    467 
    468   PROCESS_INFORMATION temp_process_info = {};
    469   BOOL result = CreateProcessAsUser(user_token,
    470                                     application_name.c_str(),
    471                                     const_cast<LPWSTR>(command_line.c_str()),
    472                                     process_attributes,
    473                                     thread_attributes,
    474                                     inherit_handles,
    475                                     creation_flags,
    476                                     NULL,
    477                                     NULL,
    478                                     &startup_info,
    479                                     &temp_process_info);
    480 
    481   // CreateProcessAsUser will fail on XP and W2K3 with ERROR_PIPE_NOT_CONNECTED
    482   // if the user hasn't logged to the target session yet. In such a case
    483   // we try to talk to the execution server directly emulating what
    484   // the undocumented and not-exported advapi32!CreateRemoteSessionProcessW()
    485   // function does. The created process will run under Winlogon'a token instead
    486   // of |user_token|. Since Winlogon runs as SYSTEM, this suits our needs.
    487   if (!result &&
    488       GetLastError() == ERROR_PIPE_NOT_CONNECTED &&
    489       base::win::GetVersion() < base::win::VERSION_VISTA) {
    490     DWORD session_id;
    491     DWORD return_length;
    492     result = GetTokenInformation(user_token,
    493                                  TokenSessionId,
    494                                  &session_id,
    495                                  sizeof(session_id),
    496                                  &return_length);
    497     if (result && session_id != 0) {
    498       result = CreateRemoteSessionProcess(session_id,
    499                                           application_name,
    500                                           command_line,
    501                                           creation_flags,
    502                                           desktop_name,
    503                                           &temp_process_info);
    504     } else {
    505       // Restore the error status returned by CreateProcessAsUser().
    506       result = FALSE;
    507       SetLastError(ERROR_PIPE_NOT_CONNECTED);
    508     }
    509   }
    510 
    511   if (!result) {
    512     PLOG(ERROR) << "Failed to launch a process with a user token";
    513     return false;
    514   }
    515 
    516   base::win::ScopedProcessInformation process_info(temp_process_info);
    517 
    518   CHECK(process_info.IsValid());
    519   process_out->Set(process_info.TakeProcessHandle());
    520   thread_out->Set(process_info.TakeThreadHandle());
    521   return true;
    522 }
    523 
    524 }  // namespace remoting
    525