Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / remoting / host / win / launch_process_with_token.cc
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