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"
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
;
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
) {
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
];
76 if (win_station_query_information(0,
78 kCreateProcessPipeNameClass
,
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
,
103 if (pipe
.IsValid()) {
107 // Cannot continue retrying if error is something other than
109 if (GetLastError() != ERROR_PIPE_BUSY
) {
113 // Cannot continue retrying if wait on pipe fails.
114 if (!WaitNamedPipe(pipe_name
.c_str(), kPipeBusyWaitTimeoutMs
)) {
119 if (!pipe
.IsValid()) {
120 LOG_GETLASTERROR(ERROR
) << "Failed to connect to '" << pipe_name
<< "'";
124 *pipe_out
= pipe
.Pass();
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";
139 ScopedHandle copied_token
;
140 if (!DuplicateTokenEx(process_token
,
143 SecurityImpersonation
,
145 copied_token
.Receive())) {
146 LOG_GETLASTERROR(ERROR
) << "Failed to duplicate the process token";
150 *token_out
= copied_token
.Pass();
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
)) {
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";
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";
180 *token_out
= privileged_token
.Pass();
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
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
|
198 PROCESS_CREATE_THREAD
|
199 PROCESS_SET_SESSIONID
|
200 PROCESS_VM_OPERATION
|
204 PROCESS_CREATE_PROCESS
|
206 PROCESS_SET_INFORMATION
|
207 PROCESS_QUERY_INFORMATION
|
208 PROCESS_SUSPEND_RESUME
;
209 process_information
->hProcess
=
210 OpenProcess(desired_access
,
212 process_information
->dwProcessId
);
213 if (!process_information
->hProcess
) {
214 LOG_GETLASTERROR(ERROR
) << "Failed to open the process "
215 << process_information
->dwProcessId
;
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
|
228 THREAD_SUSPEND_RESUME
|
231 THREAD_QUERY_INFORMATION
|
232 THREAD_SET_INFORMATION
|
233 THREAD_SET_THREAD_TOKEN
|
235 THREAD_DIRECT_IMPERSONATION
;
236 process_information
->hThread
=
237 OpenThread(desired_access
,
239 process_information
->dwThreadId
);
240 if (!process_information
->hThread
) {
241 LOG_GETLASTERROR(ERROR
) << "Failed to open the thread "
242 << process_information
->dwThreadId
;
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
;
259 // Receives the response to a remote process create request.
260 bool ReceiveCreateProcessResponse(
262 PROCESS_INFORMATION
* process_information_out
) {
263 struct CreateProcessResponse
{
267 PROCESS_INFORMATION process_information
;
271 CreateProcessResponse response
;
272 if (!ReadFile(pipe
, &response
, sizeof(response
), &bytes
, NULL
)) {
273 LOG_GETLASTERROR(ERROR
) << "Failed to receive CreateProcessAsUser response";
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
);
284 if (!response
.success
) {
285 SetLastError(response
.last_error
);
289 *process_information_out
= response
.process_information
;
293 // Sends a remote process create request to the execution server.
294 bool SendCreateProcessRequest(
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
305 struct CreateProcessRequest
{
308 BOOL use_default_token
;
310 LPWSTR application_name
;
312 SECURITY_ATTRIBUTES process_attributes
;
313 SECURITY_ATTRIBUTES thread_attributes
;
314 BOOL inherit_handles
;
315 DWORD creation_flags
;
317 LPWSTR current_directory
;
318 STARTUPINFOW startup_info
;
319 PROCESS_INFORMATION process_information
;
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(),
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(),
362 reinterpret_cast<wchar_t*>(buffer
.get() + buffer_offset
));
364 // Pass the request to create a process in the target session.
366 if (!WriteFile(pipe
, buffer
.get(), size
, &bytes
, NULL
)) {
367 LOG_GETLASTERROR(ERROR
) << "Failed to send CreateProcessAsUser request";
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(
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
))
391 if (!SendCreateProcessRequest(pipe
, application_name
, command_line
,
392 creation_flags
, desktop_name
)) {
396 PROCESS_INFORMATION process_information
;
397 if (!ReceiveCreateProcessResponse(pipe
, &process_information
))
400 if (!ProcessCreateProcessResponse(creation_flags
, &process_information
)) {
401 CloseHandlesAndTerminateProcess(&process_information
);
405 *process_information_out
= process_information
;
413 // Pipe name prefix used by Chrome IPC channels to convert a channel name into
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.
429 if (!CreateIpcChannel(channel_name
, pipe_security_descriptor
, &pipe
)) {
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
,
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
452 client
.Set(CreateFile(UTF8ToUTF16(pipe_name
).c_str(),
453 GENERIC_READ
| GENERIC_WRITE
,
455 &security_attributes
,
457 SECURITY_SQOS_PRESENT
| SECURITY_IDENTIFICATION
|
458 FILE_FLAG_OVERLAPPED
,
460 if (!client
.IsValid()) {
461 LOG_GETLASTERROR(ERROR
) << "Failed to connect to '" << pipe_name
<< "'";
465 *client_out
= client
.Pass();
466 *server_out
= server
.Pass();
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
);
477 LOG_GETLASTERROR(ERROR
) <<
478 "Failed to create a security descriptor for the Chromoting IPC channel";
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
,
502 &security_attributes
));
503 if (!pipe
.IsValid()) {
504 LOG_GETLASTERROR(ERROR
) <<
505 "Failed to create the server end of the Chromoting IPC channel";
509 *pipe_out
= pipe
.Pass();
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
)) {
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
)) {
529 if (!ImpersonateLoggedOnUser(privileged_token
)) {
530 LOG_GETLASTERROR(ERROR
) <<
531 "Failed to impersonate the privileged token";
535 // Change the session ID of the token.
536 DWORD new_session_id
= session_id
;
537 if (!SetTokenInformation(session_token
,
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());
548 // Revert to the default token.
549 CHECK(RevertToSelf());
551 *token_out
= session_token
.Pass();
555 bool LaunchProcessWithToken(const FilePath
& binary
,
556 const CommandLine::StringType
& command_line
,
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
);
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()),
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.
593 GetLastError() == ERROR_PIPE_NOT_CONNECTED
&&
594 base::win::GetVersion() < base::win::VERSION_VISTA
) {
597 result
= GetTokenInformation(user_token
,
602 if (result
&& session_id
!= 0) {
603 result
= CreateRemoteSessionProcess(session_id
,
608 process_info
.Receive());
610 // Restore the error status returned by CreateProcessAsUser().
612 SetLastError(ERROR_PIPE_NOT_CONNECTED
);
617 LOG_GETLASTERROR(ERROR
) <<
618 "Failed to launch a process with a user token";
622 CHECK(process_info
.IsValid());
623 process_out
->Set(process_info
.TakeProcessHandle());
624 thread_out
->Set(process_info
.TakeThreadHandle());
628 } // namespace remoting