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/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
;
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
= NULL
;
47 if (process_information
->hProcess
) {
48 TerminateProcess(process_information
->hProcess
, CONTROL_C_EXIT
);
49 CloseHandle(process_information
->hProcess
);
50 process_information
->hProcess
= NULL
;
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
];
71 if (win_station_query_information(0,
73 kCreateProcessPipeNameClass
,
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
,
102 // Cannot continue retrying if error is something other than
104 if (GetLastError() != ERROR_PIPE_BUSY
) {
108 // Cannot continue retrying if wait on pipe fails.
109 if (!WaitNamedPipe(pipe_name
.c_str(), kPipeBusyWaitTimeoutMs
)) {
114 if (!pipe
.IsValid()) {
115 PLOG(ERROR
) << "Failed to connect to '" << pipe_name
<< "'";
119 *pipe_out
= pipe
.Pass();
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
) {
127 if (!OpenProcessToken(GetCurrentProcess(),
128 TOKEN_DUPLICATE
| desired_access
,
130 PLOG(ERROR
) << "Failed to open process token";
133 ScopedHandle
process_token(temp_handle
);
135 if (!DuplicateTokenEx(process_token
,
138 SecurityImpersonation
,
141 PLOG(ERROR
) << "Failed to duplicate the process token";
145 token_out
->Set(temp_handle
);
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
)) {
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";
167 // Enable the SE_TCB_NAME privilege.
168 if (!AdjustTokenPrivileges(privileged_token
, FALSE
, &state
, 0, NULL
, 0)) {
169 PLOG(ERROR
) << "Failed to enable SE_TCB_NAME privilege in a token";
173 *token_out
= privileged_token
.Pass();
177 // Fills the process and thread handles in the passed |process_information|
178 // structure and resume the process if the caller didn't want to suspend it.
179 bool ProcessCreateProcessResponse(DWORD creation_flags
,
180 PROCESS_INFORMATION
* process_information
) {
181 // The execution server does not return handles to the created process and
183 if (!process_information
->hProcess
) {
184 // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of
185 // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from
186 // the XP version of the SDK.
187 DWORD desired_access
=
188 STANDARD_RIGHTS_REQUIRED
|
191 PROCESS_CREATE_THREAD
|
192 PROCESS_SET_SESSIONID
|
193 PROCESS_VM_OPERATION
|
197 PROCESS_CREATE_PROCESS
|
199 PROCESS_SET_INFORMATION
|
200 PROCESS_QUERY_INFORMATION
|
201 PROCESS_SUSPEND_RESUME
;
202 process_information
->hProcess
=
203 OpenProcess(desired_access
,
205 process_information
->dwProcessId
);
206 if (!process_information
->hProcess
) {
207 PLOG(ERROR
) << "Failed to open the process "
208 << process_information
->dwProcessId
;
213 if (!process_information
->hThread
) {
214 // N.B. THREAD_ALL_ACCESS is different in XP and Vista+ versions of
215 // the SDK. |desired_access| below is effectively THREAD_ALL_ACCESS from
216 // the XP version of the SDK.
217 DWORD desired_access
=
218 STANDARD_RIGHTS_REQUIRED
|
221 THREAD_SUSPEND_RESUME
|
224 THREAD_QUERY_INFORMATION
|
225 THREAD_SET_INFORMATION
|
226 THREAD_SET_THREAD_TOKEN
|
228 THREAD_DIRECT_IMPERSONATION
;
229 process_information
->hThread
=
230 OpenThread(desired_access
,
232 process_information
->dwThreadId
);
233 if (!process_information
->hThread
) {
234 PLOG(ERROR
) << "Failed to open the thread "
235 << process_information
->dwThreadId
;
240 // Resume the thread if the caller didn't want to suspend the process.
241 if ((creation_flags
& CREATE_SUSPENDED
) == 0) {
242 if (!ResumeThread(process_information
->hThread
)) {
243 PLOG(ERROR
) << "Failed to resume the thread "
244 << process_information
->dwThreadId
;
252 // Receives the response to a remote process create request.
253 bool ReceiveCreateProcessResponse(
255 PROCESS_INFORMATION
* process_information_out
) {
256 struct CreateProcessResponse
{
260 PROCESS_INFORMATION process_information
;
264 CreateProcessResponse response
;
265 if (!ReadFile(pipe
, &response
, sizeof(response
), &bytes
, NULL
)) {
266 PLOG(ERROR
) << "Failed to receive CreateProcessAsUser response";
270 // The server sends the data in one chunk so if we didn't received a complete
271 // answer something bad happend and there is no point in retrying.
272 if (bytes
!= sizeof(response
)) {
273 SetLastError(ERROR_RECEIVE_PARTIAL
);
277 if (!response
.success
) {
278 SetLastError(response
.last_error
);
282 *process_information_out
= response
.process_information
;
286 // Sends a remote process create request to the execution server.
287 bool SendCreateProcessRequest(
289 const base::FilePath::StringType
& application_name
,
290 const base::CommandLine::StringType
& command_line
,
291 DWORD creation_flags
,
292 const base::char16
* desktop_name
) {
293 // |CreateProcessRequest| structure passes the same parameters to
294 // the execution server as CreateProcessAsUser() function does. Strings are
295 // stored as wide strings immediately after the structure. String pointers are
296 // represented as byte offsets to string data from the beginning of
298 struct CreateProcessRequest
{
301 BOOL use_default_token
;
303 LPWSTR application_name
;
305 SECURITY_ATTRIBUTES process_attributes
;
306 SECURITY_ATTRIBUTES thread_attributes
;
307 BOOL inherit_handles
;
308 DWORD creation_flags
;
310 LPWSTR current_directory
;
311 STARTUPINFOW startup_info
;
312 PROCESS_INFORMATION process_information
;
315 base::string16 desktop
;
317 desktop
= desktop_name
;
319 // Allocate a large enough buffer to hold the CreateProcessRequest structure
320 // and three NULL-terminated string parameters.
321 size_t size
= sizeof(CreateProcessRequest
) + sizeof(wchar_t) *
322 (application_name
.size() + command_line
.size() + desktop
.size() + 3);
323 scoped_ptr
<char[]> buffer(new char[size
]);
324 memset(buffer
.get(), 0, size
);
326 // Marshal the input parameters.
327 CreateProcessRequest
* request
=
328 reinterpret_cast<CreateProcessRequest
*>(buffer
.get());
329 request
->size
= size
;
330 request
->process_id
= GetCurrentProcessId();
331 request
->use_default_token
= TRUE
;
332 // Always pass CREATE_SUSPENDED to avoid a race between the created process
333 // exiting too soon and OpenProcess() call below.
334 request
->creation_flags
= creation_flags
| CREATE_SUSPENDED
;
335 request
->startup_info
.cb
= sizeof(request
->startup_info
);
337 size_t buffer_offset
= sizeof(CreateProcessRequest
);
339 request
->application_name
= reinterpret_cast<LPWSTR
>(buffer_offset
);
340 std::copy(application_name
.begin(),
341 application_name
.end(),
342 reinterpret_cast<wchar_t*>(buffer
.get() + buffer_offset
));
343 buffer_offset
+= (application_name
.size() + 1) * sizeof(wchar_t);
345 request
->command_line
= reinterpret_cast<LPWSTR
>(buffer_offset
);
346 std::copy(command_line
.begin(),
348 reinterpret_cast<wchar_t*>(buffer
.get() + buffer_offset
));
349 buffer_offset
+= (command_line
.size() + 1) * sizeof(wchar_t);
351 request
->startup_info
.lpDesktop
=
352 reinterpret_cast<LPWSTR
>(buffer_offset
);
353 std::copy(desktop
.begin(),
355 reinterpret_cast<wchar_t*>(buffer
.get() + buffer_offset
));
357 // Pass the request to create a process in the target session.
359 if (!WriteFile(pipe
, buffer
.get(), size
, &bytes
, NULL
)) {
360 PLOG(ERROR
) << "Failed to send CreateProcessAsUser request";
367 // Requests the execution server to create a process in the specified session
368 // using the default (i.e. Winlogon) token. This routine relies on undocumented
369 // OS functionality and will likely not work on anything but XP or W2K3.
370 bool CreateRemoteSessionProcess(
372 const base::FilePath::StringType
& application_name
,
373 const base::CommandLine::StringType
& command_line
,
374 DWORD creation_flags
,
375 const base::char16
* desktop_name
,
376 PROCESS_INFORMATION
* process_information_out
) {
377 DCHECK_LT(base::win::GetVersion(), base::win::VERSION_VISTA
);
379 base::win::ScopedHandle pipe
;
380 if (!ConnectToExecutionServer(session_id
, &pipe
))
383 if (!SendCreateProcessRequest(pipe
, application_name
, command_line
,
384 creation_flags
, desktop_name
)) {
388 PROCESS_INFORMATION process_information
;
389 if (!ReceiveCreateProcessResponse(pipe
, &process_information
))
392 if (!ProcessCreateProcessResponse(creation_flags
, &process_information
)) {
393 CloseHandlesAndTerminateProcess(&process_information
);
397 *process_information_out
= process_information
;
405 base::LazyInstance
<base::Lock
>::Leaky g_inherit_handles_lock
=
406 LAZY_INSTANCE_INITIALIZER
;
408 // Creates a copy of the current process token for the given |session_id| so
409 // it can be used to launch a process in that session.
410 bool CreateSessionToken(uint32 session_id
, ScopedHandle
* token_out
) {
411 ScopedHandle session_token
;
412 DWORD desired_access
= TOKEN_ADJUST_DEFAULT
| TOKEN_ADJUST_SESSIONID
|
413 TOKEN_ASSIGN_PRIMARY
| TOKEN_DUPLICATE
| TOKEN_QUERY
;
414 if (!CopyProcessToken(desired_access
, &session_token
)) {
418 // Temporarily enable the SE_TCB_NAME privilege as it is required by
419 // SetTokenInformation(TokenSessionId).
420 ScopedHandle privileged_token
;
421 if (!CreatePrivilegedToken(&privileged_token
)) {
424 if (!ImpersonateLoggedOnUser(privileged_token
)) {
425 PLOG(ERROR
) << "Failed to impersonate the privileged token";
429 // Change the session ID of the token.
430 DWORD new_session_id
= session_id
;
431 if (!SetTokenInformation(session_token
,
434 sizeof(new_session_id
))) {
435 PLOG(ERROR
) << "Failed to change session ID of a token";
437 // Revert to the default token.
438 CHECK(RevertToSelf());
442 // Revert to the default token.
443 CHECK(RevertToSelf());
445 *token_out
= session_token
.Pass();
449 bool LaunchProcessWithToken(const base::FilePath
& binary
,
450 const base::CommandLine::StringType
& command_line
,
452 SECURITY_ATTRIBUTES
* process_attributes
,
453 SECURITY_ATTRIBUTES
* thread_attributes
,
454 bool inherit_handles
,
455 DWORD creation_flags
,
456 const base::char16
* desktop_name
,
457 ScopedHandle
* process_out
,
458 ScopedHandle
* thread_out
) {
459 base::FilePath::StringType application_name
= binary
.value();
461 STARTUPINFOW startup_info
;
462 memset(&startup_info
, 0, sizeof(startup_info
));
463 startup_info
.cb
= sizeof(startup_info
);
465 startup_info
.lpDesktop
= const_cast<base::char16
*>(desktop_name
);
467 PROCESS_INFORMATION temp_process_info
= {};
468 BOOL result
= CreateProcessAsUser(user_token
,
469 application_name
.c_str(),
470 const_cast<LPWSTR
>(command_line
.c_str()),
480 // CreateProcessAsUser will fail on XP and W2K3 with ERROR_PIPE_NOT_CONNECTED
481 // if the user hasn't logged to the target session yet. In such a case
482 // we try to talk to the execution server directly emulating what
483 // the undocumented and not-exported advapi32!CreateRemoteSessionProcessW()
484 // function does. The created process will run under Winlogon'a token instead
485 // of |user_token|. Since Winlogon runs as SYSTEM, this suits our needs.
487 GetLastError() == ERROR_PIPE_NOT_CONNECTED
&&
488 base::win::GetVersion() < base::win::VERSION_VISTA
) {
491 result
= GetTokenInformation(user_token
,
496 if (result
&& session_id
!= 0) {
497 result
= CreateRemoteSessionProcess(session_id
,
504 // Restore the error status returned by CreateProcessAsUser().
506 SetLastError(ERROR_PIPE_NOT_CONNECTED
);
511 PLOG(ERROR
) << "Failed to launch a process with a user token";
515 base::win::ScopedProcessInformation
process_info(temp_process_info
);
517 CHECK(process_info
.IsValid());
518 process_out
->Set(process_info
.TakeProcessHandle());
519 thread_out
->Set(process_info
.TakeThreadHandle());
523 } // namespace remoting