Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / host / win / launch_process_with_token.cc
blobc0ee4b89e5f73fd6aa86410eb9bccae7f576b565
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.
5 #include "remoting/host/win/launch_process_with_token.h"
7 #include <windows.h>
8 #include <winternl.h>
10 #include <limits>
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"
23 using base::win::ScopedHandle;
25 namespace {
27 const char kCreateProcessDefaultPipeNameFormat[] =
28 "\\\\.\\Pipe\\TerminalServer\\SystemExecSrvr\\%d";
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);
36 const int kPipeBusyWaitTimeoutMs = 2000;
37 const int kPipeConnectMaxAttempts = 3;
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 = nullptr;
47 if (process_information->hProcess) {
48 TerminateProcess(process_information->hProcess, CONTROL_C_EXIT);
49 CloseHandle(process_information->hProcess);
50 process_information->hProcess = nullptr;
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;
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);
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));
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,
94 nullptr,
95 OPEN_EXISTING,
97 nullptr));
98 if (pipe.IsValid()) {
99 break;
102 // Cannot continue retrying if error is something other than
103 // ERROR_PIPE_BUSY.
104 if (GetLastError() != ERROR_PIPE_BUSY) {
105 break;
108 // Cannot continue retrying if wait on pipe fails.
109 if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) {
110 break;
114 if (!pipe.IsValid()) {
115 PLOG(ERROR) << "Failed to connect to '" << pipe_name << "'";
116 return false;
119 *pipe_out = pipe.Pass();
120 return true;
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;
133 ScopedHandle process_token(temp_handle);
135 if (!DuplicateTokenEx(process_token.Get(),
136 desired_access,
137 nullptr,
138 SecurityImpersonation,
139 TokenPrimary,
140 &temp_handle)) {
141 PLOG(ERROR) << "Failed to duplicate the process token";
142 return false;
145 token_out->Set(temp_handle);
146 return true;
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;
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(nullptr, SE_TCB_NAME, &state.Privileges[0].Luid)) {
163 PLOG(ERROR) << "Failed to lookup the LUID for the SE_TCB_NAME privilege";
164 return false;
167 // Enable the SE_TCB_NAME privilege.
168 if (!AdjustTokenPrivileges(privileged_token.Get(), FALSE, &state, 0, nullptr,
169 0)) {
170 PLOG(ERROR) << "Failed to enable SE_TCB_NAME privilege in a token";
171 return false;
174 *token_out = privileged_token.Pass();
175 return true;
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;
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;
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;
250 return true;
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;
264 DWORD bytes;
265 CreateProcessResponse response;
266 if (!ReadFile(pipe, &response, sizeof(response), &bytes, nullptr)) {
267 PLOG(ERROR) << "Failed to receive CreateProcessAsUser response";
268 return false;
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;
278 if (!response.success) {
279 SetLastError(response.last_error);
280 return false;
283 *process_information_out = response.process_information;
284 return true;
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;
316 base::string16 desktop;
317 if (desktop_name)
318 desktop = desktop_name;
320 // Allocate a large enough buffer to hold the CreateProcessRequest structure
321 // and three nullptr-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);
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);
338 size_t buffer_offset = sizeof(CreateProcessRequest);
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);
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);
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));
358 // Pass the request to create a process in the target session.
359 DWORD bytes;
360 if (!WriteFile(pipe, buffer.get(), size, &bytes, nullptr)) {
361 PLOG(ERROR) << "Failed to send CreateProcessAsUser request";
362 return false;
365 return true;
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);
380 base::win::ScopedHandle pipe;
381 if (!ConnectToExecutionServer(session_id, &pipe))
382 return false;
384 if (!SendCreateProcessRequest(pipe.Get(), application_name, command_line,
385 creation_flags, desktop_name)) {
386 return false;
389 PROCESS_INFORMATION process_information;
390 if (!ReceiveCreateProcessResponse(pipe.Get(), &process_information))
391 return false;
393 if (!ProcessCreateProcessResponse(creation_flags, &process_information)) {
394 CloseHandlesAndTerminateProcess(&process_information);
395 return false;
398 *process_information_out = process_information;
399 return true;
402 } // namespace
404 namespace remoting {
406 base::LazyInstance<base::Lock>::Leaky g_inherit_handles_lock =
407 LAZY_INSTANCE_INITIALIZER;
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;
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;
425 if (!ImpersonateLoggedOnUser(privileged_token.Get())) {
426 PLOG(ERROR) << "Failed to impersonate the privileged token";
427 return false;
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";
438 // Revert to the default token.
439 CHECK(RevertToSelf());
440 return false;
443 // Revert to the default token.
444 CHECK(RevertToSelf());
446 *token_out = session_token.Pass();
447 return true;
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();
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);
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 nullptr,
477 nullptr,
478 &startup_info,
479 &temp_process_info);
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);
511 if (!result) {
512 PLOG(ERROR) << "Failed to launch a process with a user token";
513 return false;
516 base::win::ScopedProcessInformation process_info(temp_process_info);
518 CHECK(process_info.IsValid());
519 process_out->Set(process_info.TakeProcessHandle());
520 thread_out->Set(process_info.TakeThreadHandle());
521 return true;
524 } // namespace remoting