Move generic_handler.* to content\browser\webui since it's needed by all webui pages.
[chromium-blink-merge.git] / remoting / host / win / launch_process_with_token.cc
blob51b390034fe7be605170b3fcd4ea06d8d2dcecf5
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/process_util.h"
15 #include "base/rand_util.h"
16 #include "base/scoped_native_library.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/string16.h"
19 #include "base/stringprintf.h"
20 #include "base/utf_string_conversions.h"
21 #include "base/win/scoped_handle.h"
22 #include "base/win/scoped_process_information.h"
23 #include "base/win/windows_version.h"
24 #include "ipc/ipc_channel.h"
25 #include "ipc/ipc_channel_proxy.h"
26 #include "ipc/ipc_channel.h"
27 #include "remoting/host/win/security_descriptor.h"
29 using base::win::ScopedHandle;
31 namespace {
33 const char kCreateProcessDefaultPipeNameFormat[] =
34 "\\\\.\\Pipe\\TerminalServer\\SystemExecSrvr\\%d";
36 // Undocumented WINSTATIONINFOCLASS value causing
37 // winsta!WinStationQueryInformationW() to return the name of the pipe for
38 // requesting cross-session process creation.
39 const WINSTATIONINFOCLASS kCreateProcessPipeNameClass =
40 static_cast<WINSTATIONINFOCLASS>(0x21);
42 const int kPipeBusyWaitTimeoutMs = 2000;
43 const int kPipeConnectMaxAttempts = 3;
45 // Terminates the process and closes process and thread handles in
46 // |process_information| structure.
47 void CloseHandlesAndTerminateProcess(PROCESS_INFORMATION* process_information) {
48 if (process_information->hThread) {
49 CloseHandle(process_information->hThread);
50 process_information->hThread = NULL;
53 if (process_information->hProcess) {
54 TerminateProcess(process_information->hProcess, CONTROL_C_EXIT);
55 CloseHandle(process_information->hProcess);
56 process_information->hProcess = NULL;
60 // Connects to the executor server corresponding to |session_id|.
61 bool ConnectToExecutionServer(uint32 session_id,
62 base::win::ScopedHandle* pipe_out) {
63 string16 pipe_name;
65 // Use winsta!WinStationQueryInformationW() to determine the process creation
66 // pipe name for the session.
67 FilePath winsta_path(base::GetNativeLibraryName(UTF8ToUTF16("winsta")));
68 base::ScopedNativeLibrary winsta(winsta_path);
69 if (winsta.is_valid()) {
70 PWINSTATIONQUERYINFORMATIONW win_station_query_information =
71 static_cast<PWINSTATIONQUERYINFORMATIONW>(
72 winsta.GetFunctionPointer("WinStationQueryInformationW"));
73 if (win_station_query_information) {
74 wchar_t name[MAX_PATH];
75 ULONG name_length;
76 if (win_station_query_information(0,
77 session_id,
78 kCreateProcessPipeNameClass,
79 name,
80 sizeof(name),
81 &name_length)) {
82 pipe_name.assign(name);
87 // Use the default pipe name if we couldn't query its name.
88 if (pipe_name.empty()) {
89 pipe_name = UTF8ToUTF16(
90 StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id));
93 // Try to connect to the named pipe.
94 base::win::ScopedHandle pipe;
95 for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
96 pipe.Set(CreateFile(pipe_name.c_str(),
97 GENERIC_READ | GENERIC_WRITE,
99 NULL,
100 OPEN_EXISTING,
102 NULL));
103 if (pipe.IsValid()) {
104 break;
107 // Cannot continue retrying if error is something other than
108 // ERROR_PIPE_BUSY.
109 if (GetLastError() != ERROR_PIPE_BUSY) {
110 break;
113 // Cannot continue retrying if wait on pipe fails.
114 if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) {
115 break;
119 if (!pipe.IsValid()) {
120 LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'";
121 return false;
124 *pipe_out = pipe.Pass();
125 return true;
128 // Copies the process token making it a primary impersonation token.
129 // The returned handle will have |desired_access| rights.
130 bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) {
131 ScopedHandle process_token;
132 if (!OpenProcessToken(GetCurrentProcess(),
133 TOKEN_DUPLICATE | desired_access,
134 process_token.Receive())) {
135 LOG_GETLASTERROR(ERROR) << "Failed to open process token";
136 return false;
139 ScopedHandle copied_token;
140 if (!DuplicateTokenEx(process_token,
141 desired_access,
142 NULL,
143 SecurityImpersonation,
144 TokenPrimary,
145 copied_token.Receive())) {
146 LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token";
147 return false;
150 *token_out = copied_token.Pass();
151 return true;
154 // Creates a copy of the current process with SE_TCB_NAME privilege enabled.
155 bool CreatePrivilegedToken(ScopedHandle* token_out) {
156 ScopedHandle privileged_token;
157 DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE |
158 TOKEN_DUPLICATE | TOKEN_QUERY;
159 if (!CopyProcessToken(desired_access, &privileged_token)) {
160 return false;
163 // Get the LUID for the SE_TCB_NAME privilege.
164 TOKEN_PRIVILEGES state;
165 state.PrivilegeCount = 1;
166 state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
167 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) {
168 LOG_GETLASTERROR(ERROR) <<
169 "Failed to lookup the LUID for the SE_TCB_NAME privilege";
170 return false;
173 // Enable the SE_TCB_NAME privilege.
174 if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) {
175 LOG_GETLASTERROR(ERROR) <<
176 "Failed to enable SE_TCB_NAME privilege in a token";
177 return false;
180 *token_out = privileged_token.Pass();
181 return true;
184 // Fills the process and thread handles in the passed |process_information|
185 // structure and resume the process if the caller didn't want to suspend it.
186 bool ProcessCreateProcessResponse(DWORD creation_flags,
187 PROCESS_INFORMATION* process_information) {
188 // The execution server does not return handles to the created process and
189 // thread.
190 if (!process_information->hProcess) {
191 // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of
192 // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from
193 // the XP version of the SDK.
194 DWORD desired_access =
195 STANDARD_RIGHTS_REQUIRED |
196 SYNCHRONIZE |
197 PROCESS_TERMINATE |
198 PROCESS_CREATE_THREAD |
199 PROCESS_SET_SESSIONID |
200 PROCESS_VM_OPERATION |
201 PROCESS_VM_READ |
202 PROCESS_VM_WRITE |
203 PROCESS_DUP_HANDLE |
204 PROCESS_CREATE_PROCESS |
205 PROCESS_SET_QUOTA |
206 PROCESS_SET_INFORMATION |
207 PROCESS_QUERY_INFORMATION |
208 PROCESS_SUSPEND_RESUME;
209 process_information->hProcess =
210 OpenProcess(desired_access,
211 FALSE,
212 process_information->dwProcessId);
213 if (!process_information->hProcess) {
214 LOG_GETLASTERROR(ERROR) << "Failed to open the process "
215 << process_information->dwProcessId;
216 return false;
220 if (!process_information->hThread) {
221 // N.B. THREAD_ALL_ACCESS is different in XP and Vista+ versions of
222 // the SDK. |desired_access| below is effectively THREAD_ALL_ACCESS from
223 // the XP version of the SDK.
224 DWORD desired_access =
225 STANDARD_RIGHTS_REQUIRED |
226 SYNCHRONIZE |
227 THREAD_TERMINATE |
228 THREAD_SUSPEND_RESUME |
229 THREAD_GET_CONTEXT |
230 THREAD_SET_CONTEXT |
231 THREAD_QUERY_INFORMATION |
232 THREAD_SET_INFORMATION |
233 THREAD_SET_THREAD_TOKEN |
234 THREAD_IMPERSONATE |
235 THREAD_DIRECT_IMPERSONATION;
236 process_information->hThread =
237 OpenThread(desired_access,
238 FALSE,
239 process_information->dwThreadId);
240 if (!process_information->hThread) {
241 LOG_GETLASTERROR(ERROR) << "Failed to open the thread "
242 << process_information->dwThreadId;
243 return false;
247 // Resume the thread if the caller didn't want to suspend the process.
248 if ((creation_flags & CREATE_SUSPENDED) == 0) {
249 if (!ResumeThread(process_information->hThread)) {
250 LOG_GETLASTERROR(ERROR) << "Failed to resume the thread "
251 << process_information->dwThreadId;
252 return false;
256 return true;
259 // Receives the response to a remote process create request.
260 bool ReceiveCreateProcessResponse(
261 HANDLE pipe,
262 PROCESS_INFORMATION* process_information_out) {
263 struct CreateProcessResponse {
264 DWORD size;
265 BOOL success;
266 DWORD last_error;
267 PROCESS_INFORMATION process_information;
270 DWORD bytes;
271 CreateProcessResponse response;
272 if (!ReadFile(pipe, &response, sizeof(response), &bytes, NULL)) {
273 LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response";
274 return false;
277 // The server sends the data in one chunk so if we didn't received a complete
278 // answer something bad happend and there is no point in retrying.
279 if (bytes != sizeof(response)) {
280 SetLastError(ERROR_RECEIVE_PARTIAL);
281 return false;
284 if (!response.success) {
285 SetLastError(response.last_error);
286 return false;
289 *process_information_out = response.process_information;
290 return true;
293 // Sends a remote process create request to the execution server.
294 bool SendCreateProcessRequest(
295 HANDLE pipe,
296 const FilePath::StringType& application_name,
297 const CommandLine::StringType& command_line,
298 DWORD creation_flags,
299 const char16* desktop_name) {
300 // |CreateProcessRequest| structure passes the same parameters to
301 // the execution server as CreateProcessAsUser() function does. Strings are
302 // stored as wide strings immediately after the structure. String pointers are
303 // represented as byte offsets to string data from the beginning of
304 // the structure.
305 struct CreateProcessRequest {
306 DWORD size;
307 DWORD process_id;
308 BOOL use_default_token;
309 HANDLE token;
310 LPWSTR application_name;
311 LPWSTR command_line;
312 SECURITY_ATTRIBUTES process_attributes;
313 SECURITY_ATTRIBUTES thread_attributes;
314 BOOL inherit_handles;
315 DWORD creation_flags;
316 LPVOID environment;
317 LPWSTR current_directory;
318 STARTUPINFOW startup_info;
319 PROCESS_INFORMATION process_information;
322 string16 desktop;
323 if (desktop_name)
324 desktop = desktop_name;
326 // Allocate a large enough buffer to hold the CreateProcessRequest structure
327 // and three NULL-terminated string parameters.
328 size_t size = sizeof(CreateProcessRequest) + sizeof(wchar_t) *
329 (application_name.size() + command_line.size() + desktop.size() + 3);
330 scoped_array<char> buffer(new char[size]);
331 memset(buffer.get(), 0, size);
333 // Marshal the input parameters.
334 CreateProcessRequest* request =
335 reinterpret_cast<CreateProcessRequest*>(buffer.get());
336 request->size = size;
337 request->process_id = GetCurrentProcessId();
338 request->use_default_token = TRUE;
339 // Always pass CREATE_SUSPENDED to avoid a race between the created process
340 // exiting too soon and OpenProcess() call below.
341 request->creation_flags = creation_flags | CREATE_SUSPENDED;
342 request->startup_info.cb = sizeof(request->startup_info);
344 size_t buffer_offset = sizeof(CreateProcessRequest);
346 request->application_name = reinterpret_cast<LPWSTR>(buffer_offset);
347 std::copy(application_name.begin(),
348 application_name.end(),
349 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset));
350 buffer_offset += (application_name.size() + 1) * sizeof(wchar_t);
352 request->command_line = reinterpret_cast<LPWSTR>(buffer_offset);
353 std::copy(command_line.begin(),
354 command_line.end(),
355 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset));
356 buffer_offset += (command_line.size() + 1) * sizeof(wchar_t);
358 request->startup_info.lpDesktop =
359 reinterpret_cast<LPWSTR>(buffer_offset);
360 std::copy(desktop.begin(),
361 desktop.end(),
362 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset));
364 // Pass the request to create a process in the target session.
365 DWORD bytes;
366 if (!WriteFile(pipe, buffer.get(), size, &bytes, NULL)) {
367 LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request";
368 return false;
371 return true;
374 // Requests the execution server to create a process in the specified session
375 // using the default (i.e. Winlogon) token. This routine relies on undocumented
376 // OS functionality and will likely not work on anything but XP or W2K3.
377 bool CreateRemoteSessionProcess(
378 uint32 session_id,
379 const FilePath::StringType& application_name,
380 const CommandLine::StringType& command_line,
381 DWORD creation_flags,
382 const char16* desktop_name,
383 PROCESS_INFORMATION* process_information_out)
385 DCHECK_LT(base::win::GetVersion(), base::win::VERSION_VISTA);
387 base::win::ScopedHandle pipe;
388 if (!ConnectToExecutionServer(session_id, &pipe))
389 return false;
391 if (!SendCreateProcessRequest(pipe, application_name, command_line,
392 creation_flags, desktop_name)) {
393 return false;
396 PROCESS_INFORMATION process_information;
397 if (!ReceiveCreateProcessResponse(pipe, &process_information))
398 return false;
400 if (!ProcessCreateProcessResponse(creation_flags, &process_information)) {
401 CloseHandlesAndTerminateProcess(&process_information);
402 return false;
405 *process_information_out = process_information;
406 return true;
409 } // namespace
411 namespace remoting {
413 // Pipe name prefix used by Chrome IPC channels to convert a channel name into
414 // a pipe name.
415 const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome.";
417 base::LazyInstance<base::Lock>::Leaky g_inherit_handles_lock =
418 LAZY_INSTANCE_INITIALIZER;
420 bool CreateConnectedIpcChannel(
421 const std::string& channel_name,
422 const std::string& pipe_security_descriptor,
423 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
424 IPC::Listener* delegate,
425 base::win::ScopedHandle* client_out,
426 scoped_ptr<IPC::ChannelProxy>* server_out) {
427 // Create the server end of the channel.
428 ScopedHandle pipe;
429 if (!CreateIpcChannel(channel_name, pipe_security_descriptor, &pipe)) {
430 return false;
433 // Wrap the pipe into an IPC channel.
434 scoped_ptr<IPC::ChannelProxy> server(new IPC::ChannelProxy(
435 IPC::ChannelHandle(pipe),
436 IPC::Channel::MODE_SERVER,
437 delegate,
438 io_task_runner));
440 // Convert the channel name to the pipe name.
441 std::string pipe_name(remoting::kChromePipeNamePrefix);
442 pipe_name.append(channel_name);
444 SECURITY_ATTRIBUTES security_attributes = {0};
445 security_attributes.nLength = sizeof(security_attributes);
446 security_attributes.lpSecurityDescriptor = NULL;
447 security_attributes.bInheritHandle = TRUE;
449 // Create the client end of the channel. This code should match the code in
450 // IPC::Channel.
451 ScopedHandle client;
452 client.Set(CreateFile(UTF8ToUTF16(pipe_name).c_str(),
453 GENERIC_READ | GENERIC_WRITE,
455 &security_attributes,
456 OPEN_EXISTING,
457 SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION |
458 FILE_FLAG_OVERLAPPED,
459 NULL));
460 if (!client.IsValid()) {
461 LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'";
462 return false;
465 *client_out = client.Pass();
466 *server_out = server.Pass();
467 return true;
470 bool CreateIpcChannel(
471 const std::string& channel_name,
472 const std::string& pipe_security_descriptor,
473 base::win::ScopedHandle* pipe_out) {
474 // Create security descriptor for the channel.
475 ScopedSd sd = ConvertSddlToSd(pipe_security_descriptor);
476 if (!sd) {
477 LOG_GETLASTERROR(ERROR) <<
478 "Failed to create a security descriptor for the Chromoting IPC channel";
479 return false;
482 SECURITY_ATTRIBUTES security_attributes = {0};
483 security_attributes.nLength = sizeof(security_attributes);
484 security_attributes.lpSecurityDescriptor = sd.get();
485 security_attributes.bInheritHandle = FALSE;
487 // Convert the channel name to the pipe name.
488 std::string pipe_name(kChromePipeNamePrefix);
489 pipe_name.append(channel_name);
491 // Create the server end of the pipe. This code should match the code in
492 // IPC::Channel with exception of passing a non-default security descriptor.
493 base::win::ScopedHandle pipe;
494 pipe.Set(CreateNamedPipe(
495 UTF8ToUTF16(pipe_name).c_str(),
496 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
497 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
499 IPC::Channel::kReadBufferSize,
500 IPC::Channel::kReadBufferSize,
501 5000,
502 &security_attributes));
503 if (!pipe.IsValid()) {
504 LOG_GETLASTERROR(ERROR) <<
505 "Failed to create the server end of the Chromoting IPC channel";
506 return false;
509 *pipe_out = pipe.Pass();
510 return true;
513 // Creates a copy of the current process token for the given |session_id| so
514 // it can be used to launch a process in that session.
515 bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
516 ScopedHandle session_token;
517 DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID |
518 TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY;
519 if (!CopyProcessToken(desired_access, &session_token)) {
520 return false;
523 // Temporarily enable the SE_TCB_NAME privilege as it is required by
524 // SetTokenInformation(TokenSessionId).
525 ScopedHandle privileged_token;
526 if (!CreatePrivilegedToken(&privileged_token)) {
527 return false;
529 if (!ImpersonateLoggedOnUser(privileged_token)) {
530 LOG_GETLASTERROR(ERROR) <<
531 "Failed to impersonate the privileged token";
532 return false;
535 // Change the session ID of the token.
536 DWORD new_session_id = session_id;
537 if (!SetTokenInformation(session_token,
538 TokenSessionId,
539 &new_session_id,
540 sizeof(new_session_id))) {
541 LOG_GETLASTERROR(ERROR) << "Failed to change session ID of a token";
543 // Revert to the default token.
544 CHECK(RevertToSelf());
545 return false;
548 // Revert to the default token.
549 CHECK(RevertToSelf());
551 *token_out = session_token.Pass();
552 return true;
555 bool LaunchProcessWithToken(const FilePath& binary,
556 const CommandLine::StringType& command_line,
557 HANDLE user_token,
558 SECURITY_ATTRIBUTES* process_attributes,
559 SECURITY_ATTRIBUTES* thread_attributes,
560 bool inherit_handles,
561 DWORD creation_flags,
562 const char16* desktop_name,
563 ScopedHandle* process_out,
564 ScopedHandle* thread_out) {
565 FilePath::StringType application_name = binary.value();
567 STARTUPINFOW startup_info;
568 memset(&startup_info, 0, sizeof(startup_info));
569 startup_info.cb = sizeof(startup_info);
570 if (desktop_name)
571 startup_info.lpDesktop = const_cast<char16*>(desktop_name);
573 base::win::ScopedProcessInformation process_info;
574 BOOL result = CreateProcessAsUser(user_token,
575 application_name.c_str(),
576 const_cast<LPWSTR>(command_line.c_str()),
577 process_attributes,
578 thread_attributes,
579 inherit_handles,
580 creation_flags,
581 NULL,
582 NULL,
583 &startup_info,
584 process_info.Receive());
586 // CreateProcessAsUser will fail on XP and W2K3 with ERROR_PIPE_NOT_CONNECTED
587 // if the user hasn't logged to the target session yet. In such a case
588 // we try to talk to the execution server directly emulating what
589 // the undocumented and not-exported advapi32!CreateRemoteSessionProcessW()
590 // function does. The created process will run under Winlogon'a token instead
591 // of |user_token|. Since Winlogon runs as SYSTEM, this suits our needs.
592 if (!result &&
593 GetLastError() == ERROR_PIPE_NOT_CONNECTED &&
594 base::win::GetVersion() < base::win::VERSION_VISTA) {
595 DWORD session_id;
596 DWORD return_length;
597 result = GetTokenInformation(user_token,
598 TokenSessionId,
599 &session_id,
600 sizeof(session_id),
601 &return_length);
602 if (result && session_id != 0) {
603 result = CreateRemoteSessionProcess(session_id,
604 application_name,
605 command_line,
606 creation_flags,
607 desktop_name,
608 process_info.Receive());
609 } else {
610 // Restore the error status returned by CreateProcessAsUser().
611 result = FALSE;
612 SetLastError(ERROR_PIPE_NOT_CONNECTED);
616 if (!result) {
617 LOG_GETLASTERROR(ERROR) <<
618 "Failed to launch a process with a user token";
619 return false;
622 CHECK(process_info.IsValid());
623 process_out->Set(process_info.TakeProcessHandle());
624 thread_out->Set(process_info.TakeThreadHandle());
625 return true;
628 } // namespace remoting