| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "remoting/host/win/launch_process_with_token.h" | 5 #include "remoting/host/win/launch_process_with_token.h" |
| 6 | 6 |
| 7 #include <windows.h> | 7 #include <windows.h> |
| 8 #include <winternl.h> | 8 #include <winternl.h> |
| 9 | 9 |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| (...skipping 22 matching lines...) Expand all Loading... |
| 33 const int kPipeConnectMaxAttempts = 3; | 33 const int kPipeConnectMaxAttempts = 3; |
| 34 | 34 |
| 35 // The minimum and maximum delays between attempts to inject host process into | 35 // The minimum and maximum delays between attempts to inject host process into |
| 36 // a session. | 36 // a session. |
| 37 const int kMaxLaunchDelaySeconds = 60; | 37 const int kMaxLaunchDelaySeconds = 60; |
| 38 const int kMinLaunchDelaySeconds = 1; | 38 const int kMinLaunchDelaySeconds = 1; |
| 39 | 39 |
| 40 // Name of the default session desktop. | 40 // Name of the default session desktop. |
| 41 const char kDefaultDesktopName[] = "winsta0\\default"; | 41 const char kDefaultDesktopName[] = "winsta0\\default"; |
| 42 | 42 |
| 43 // Terminates the process and closes process and thread handles in |
| 44 // |process_information| structure. |
| 45 void CloseHandlesAndTerminateProcess(PROCESS_INFORMATION* process_information) { |
| 46 if (process_information->hThread) { |
| 47 CloseHandle(process_information->hThread); |
| 48 process_information->hThread = NULL; |
| 49 } |
| 50 |
| 51 if (process_information->hProcess) { |
| 52 TerminateProcess(process_information->hProcess, CONTROL_C_EXIT); |
| 53 CloseHandle(process_information->hProcess); |
| 54 process_information->hProcess = NULL; |
| 55 } |
| 56 } |
| 57 |
| 58 // Connects to the executor server corresponding to |session_id|. |
| 59 bool ConnectToExecutionServer(uint32 session_id, |
| 60 base::win::ScopedHandle* pipe_out) { |
| 61 string16 pipe_name; |
| 62 |
| 63 // Use winsta!WinStationQueryInformationW() to determine the process creation |
| 64 // pipe name for the session. |
| 65 FilePath winsta_path(base::GetNativeLibraryName(UTF8ToUTF16("winsta"))); |
| 66 base::ScopedNativeLibrary winsta(winsta_path); |
| 67 if (winsta.is_valid()) { |
| 68 PWINSTATIONQUERYINFORMATIONW win_station_query_information = |
| 69 static_cast<PWINSTATIONQUERYINFORMATIONW>( |
| 70 winsta.GetFunctionPointer("WinStationQueryInformationW")); |
| 71 if (win_station_query_information) { |
| 72 wchar_t name[MAX_PATH]; |
| 73 ULONG name_length; |
| 74 if (win_station_query_information(0, |
| 75 session_id, |
| 76 kCreateProcessPipeNameClass, |
| 77 name, |
| 78 sizeof(name), |
| 79 &name_length)) { |
| 80 pipe_name.assign(name); |
| 81 } |
| 82 } |
| 83 } |
| 84 |
| 85 // Use the default pipe name if we couldn't query its name. |
| 86 if (pipe_name.empty()) { |
| 87 pipe_name = UTF8ToUTF16( |
| 88 StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id)); |
| 89 } |
| 90 |
| 91 // Try to connect to the named pipe. |
| 92 base::win::ScopedHandle pipe; |
| 93 for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { |
| 94 pipe.Set(CreateFile(pipe_name.c_str(), |
| 95 GENERIC_READ | GENERIC_WRITE, |
| 96 0, |
| 97 NULL, |
| 98 OPEN_EXISTING, |
| 99 0, |
| 100 NULL)); |
| 101 if (pipe.IsValid()) { |
| 102 break; |
| 103 } |
| 104 |
| 105 // Cannot continue retrying if error is something other than |
| 106 // ERROR_PIPE_BUSY. |
| 107 if (GetLastError() != ERROR_PIPE_BUSY) { |
| 108 break; |
| 109 } |
| 110 |
| 111 // Cannot continue retrying if wait on pipe fails. |
| 112 if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) { |
| 113 break; |
| 114 } |
| 115 } |
| 116 |
| 117 if (!pipe.IsValid()) { |
| 118 LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'"; |
| 119 return false; |
| 120 } |
| 121 |
| 122 *pipe_out = pipe.Pass(); |
| 123 return true; |
| 124 } |
| 125 |
| 43 // Copies the process token making it a primary impersonation token. | 126 // Copies the process token making it a primary impersonation token. |
| 44 // The returned handle will have |desired_access| rights. | 127 // The returned handle will have |desired_access| rights. |
| 45 bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) { | 128 bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) { |
| 46 ScopedHandle process_token; | 129 ScopedHandle process_token; |
| 47 if (!OpenProcessToken(GetCurrentProcess(), | 130 if (!OpenProcessToken(GetCurrentProcess(), |
| 48 TOKEN_DUPLICATE | desired_access, | 131 TOKEN_DUPLICATE | desired_access, |
| 49 process_token.Receive())) { | 132 process_token.Receive())) { |
| 50 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; | 133 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; |
| 51 return false; | 134 return false; |
| 52 } | 135 } |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 89 if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) { | 172 if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) { |
| 90 LOG_GETLASTERROR(ERROR) << | 173 LOG_GETLASTERROR(ERROR) << |
| 91 "Failed to enable SE_TCB_NAME privilege in a token"; | 174 "Failed to enable SE_TCB_NAME privilege in a token"; |
| 92 return false; | 175 return false; |
| 93 } | 176 } |
| 94 | 177 |
| 95 *token_out = privileged_token.Pass(); | 178 *token_out = privileged_token.Pass(); |
| 96 return true; | 179 return true; |
| 97 } | 180 } |
| 98 | 181 |
| 99 // Requests the execution server to create a process in the specified session | 182 // Fills the process and thread handles in the passed |process_information| |
| 100 // using the default (i.e. Winlogon) token. This routine relies on undocumented | 183 // structure and resume the process if the caller didn't want to suspend it. |
| 101 // OS functionality and will likely not work on anything but XP or W2K3. | 184 bool ProcessCreateProcessResponse(DWORD creation_flags, |
| 102 bool CreateRemoteSessionProcess( | 185 PROCESS_INFORMATION* process_information) { |
| 103 uint32 session_id, | 186 // The execution server does not return handles to the created process and |
| 104 const FilePath::StringType& application_name, | 187 // thread. |
| 105 const CommandLine::StringType& command_line, | 188 if (!process_information->hProcess) { |
| 106 DWORD creation_flags, | 189 // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of |
| 107 PROCESS_INFORMATION* process_information_out) | 190 // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from |
| 108 { | 191 // the XP version of the SDK. |
| 109 DCHECK(base::win::GetVersion() == base::win::VERSION_XP); | 192 DWORD desired_access = |
| 110 | 193 STANDARD_RIGHTS_REQUIRED | |
| 111 string16 pipe_name; | 194 SYNCHRONIZE | |
| 112 | 195 PROCESS_TERMINATE | |
| 113 // Use winsta!WinStationQueryInformationW() to determine the process creation | 196 PROCESS_CREATE_THREAD | |
| 114 // pipe name for the session. | 197 PROCESS_SET_SESSIONID | |
| 115 FilePath winsta_path(base::GetNativeLibraryName(UTF8ToUTF16("winsta"))); | 198 PROCESS_VM_OPERATION | |
| 116 base::ScopedNativeLibrary winsta(winsta_path); | 199 PROCESS_VM_READ | |
| 117 if (winsta.is_valid()) { | 200 PROCESS_VM_WRITE | |
| 118 PWINSTATIONQUERYINFORMATIONW win_station_query_information = | 201 PROCESS_DUP_HANDLE | |
| 119 static_cast<PWINSTATIONQUERYINFORMATIONW>( | 202 PROCESS_CREATE_PROCESS | |
| 120 winsta.GetFunctionPointer("WinStationQueryInformationW")); | 203 PROCESS_SET_QUOTA | |
| 121 if (win_station_query_information) { | 204 PROCESS_SET_INFORMATION | |
| 122 wchar_t name[MAX_PATH]; | 205 PROCESS_QUERY_INFORMATION | |
| 123 ULONG name_length; | 206 PROCESS_SUSPEND_RESUME; |
| 124 if (win_station_query_information(0, | 207 process_information->hProcess = |
| 125 session_id, | 208 OpenProcess(desired_access, |
| 126 kCreateProcessPipeNameClass, | 209 FALSE, |
| 127 name, | 210 process_information->dwProcessId); |
| 128 sizeof(name), | 211 if (!process_information->hProcess) { |
| 129 &name_length)) { | 212 LOG_GETLASTERROR(ERROR) << "Failed to open the process " |
| 130 pipe_name.assign(name); | 213 << process_information->dwProcessId; |
| 131 } | 214 return false; |
| 132 } | 215 } |
| 133 } | 216 } |
| 134 | 217 |
| 135 // Use the default pipe name if we couldn't query its name. | 218 if (!process_information->hThread) { |
| 136 if (pipe_name.empty()) { | 219 // N.B. THREAD_ALL_ACCESS is different in XP and Vista+ versions of |
| 137 pipe_name = UTF8ToUTF16( | 220 // the SDK. |desired_access| below is effectively THREAD_ALL_ACCESS from |
| 138 StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id)); | 221 // the XP version of the SDK. |
| 139 } | 222 DWORD desired_access = |
| 140 | 223 STANDARD_RIGHTS_REQUIRED | |
| 141 // Try to connect to the named pipe. | 224 SYNCHRONIZE | |
| 142 base::win::ScopedHandle pipe; | 225 THREAD_TERMINATE | |
| 143 for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { | 226 THREAD_SUSPEND_RESUME | |
| 144 pipe.Set(CreateFile(pipe_name.c_str(), | 227 THREAD_GET_CONTEXT | |
| 145 GENERIC_READ | GENERIC_WRITE, | 228 THREAD_SET_CONTEXT | |
| 146 0, | 229 THREAD_QUERY_INFORMATION | |
| 147 NULL, | 230 THREAD_SET_INFORMATION | |
| 148 OPEN_EXISTING, | 231 THREAD_SET_THREAD_TOKEN | |
| 149 0, | 232 THREAD_IMPERSONATE | |
| 150 NULL)); | 233 THREAD_DIRECT_IMPERSONATION; |
| 151 if (pipe.IsValid()) { | 234 process_information->hThread = |
| 152 break; | 235 OpenThread(desired_access, |
| 153 } | 236 FALSE, |
| 154 | 237 process_information->dwThreadId); |
| 155 // Cannot continue retrying if error is something other than | 238 if (!process_information->hThread) { |
| 156 // ERROR_PIPE_BUSY. | 239 LOG_GETLASTERROR(ERROR) << "Failed to open the thread " |
| 157 if (GetLastError() != ERROR_PIPE_BUSY) { | 240 << process_information->dwThreadId; |
| 158 break; | 241 return false; |
| 159 } | |
| 160 | |
| 161 // Cannot continue retrying if wait on pipe fails. | |
| 162 if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) { | |
| 163 break; | |
| 164 } | 242 } |
| 165 } | 243 } |
| 166 | 244 |
| 167 if (!pipe.IsValid()) { | 245 // Resume the thread if the caller didn't want to suspend the process. |
| 168 LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'"; | 246 if ((creation_flags & CREATE_SUSPENDED) == 0) { |
| 247 if (!ResumeThread(process_information->hThread)) { |
| 248 LOG_GETLASTERROR(ERROR) << "Failed to resume the thread " |
| 249 << process_information->dwThreadId; |
| 250 return false; |
| 251 } |
| 252 } |
| 253 |
| 254 return true; |
| 255 } |
| 256 |
| 257 // Receives the response to a remote process create request. |
| 258 bool ReceiveCreateProcessResponse( |
| 259 HANDLE pipe, |
| 260 PROCESS_INFORMATION* process_information_out) { |
| 261 struct CreateProcessResponse { |
| 262 DWORD size; |
| 263 BOOL success; |
| 264 DWORD last_error; |
| 265 PROCESS_INFORMATION process_information; |
| 266 }; |
| 267 |
| 268 DWORD bytes; |
| 269 CreateProcessResponse response; |
| 270 if (!ReadFile(pipe, &response, sizeof(response), &bytes, NULL)) { |
| 271 LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response"; |
| 169 return false; | 272 return false; |
| 170 } | 273 } |
| 171 | 274 |
| 275 // The server sends the data in one chunk so if we didn't received a complete |
| 276 // answer something bad happend and there is no point in retrying. |
| 277 if (bytes != sizeof(response)) { |
| 278 SetLastError(ERROR_RECEIVE_PARTIAL); |
| 279 return false; |
| 280 } |
| 281 |
| 282 if (!response.success) { |
| 283 SetLastError(response.last_error); |
| 284 return false; |
| 285 } |
| 286 |
| 287 *process_information_out = response.process_information; |
| 288 return true; |
| 289 } |
| 290 |
| 291 // Sends a remote process create request to the execution server. |
| 292 bool SendCreateProcessRequest( |
| 293 HANDLE pipe, |
| 294 const FilePath::StringType& application_name, |
| 295 const CommandLine::StringType& command_line, |
| 296 DWORD creation_flags) { |
| 172 string16 desktop_name(UTF8ToUTF16(kDefaultDesktopName)); | 297 string16 desktop_name(UTF8ToUTF16(kDefaultDesktopName)); |
| 173 | 298 |
| 174 // |CreateProcessRequest| structure passes the same parameters to | 299 // |CreateProcessRequest| structure passes the same parameters to |
| 175 // the execution server as CreateProcessAsUser() function does. Strings are | 300 // the execution server as CreateProcessAsUser() function does. Strings are |
| 176 // stored as wide strings immediately after the structure. String pointers are | 301 // stored as wide strings immediately after the structure. String pointers are |
| 177 // represented as byte offsets to string data from the beginning of | 302 // represented as byte offsets to string data from the beginning of |
| 178 // the structure. | 303 // the structure. |
| 179 struct CreateProcessRequest { | 304 struct CreateProcessRequest { |
| 180 DWORD size; | 305 DWORD size; |
| 181 DWORD process_id; | 306 DWORD process_id; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 199 (application_name.size() + command_line.size() + desktop_name.size() + 3); | 324 (application_name.size() + command_line.size() + desktop_name.size() + 3); |
| 200 scoped_array<char> buffer(new char[size]); | 325 scoped_array<char> buffer(new char[size]); |
| 201 memset(buffer.get(), 0, size); | 326 memset(buffer.get(), 0, size); |
| 202 | 327 |
| 203 // Marshal the input parameters. | 328 // Marshal the input parameters. |
| 204 CreateProcessRequest* request = | 329 CreateProcessRequest* request = |
| 205 reinterpret_cast<CreateProcessRequest*>(buffer.get()); | 330 reinterpret_cast<CreateProcessRequest*>(buffer.get()); |
| 206 request->size = size; | 331 request->size = size; |
| 207 request->process_id = GetCurrentProcessId(); | 332 request->process_id = GetCurrentProcessId(); |
| 208 request->use_default_token = TRUE; | 333 request->use_default_token = TRUE; |
| 209 request->creation_flags = creation_flags; | 334 // Always pass CREATE_SUSPENDED to avoid a race between the created process |
| 335 // exiting too soon and OpenProcess() call below. |
| 336 request->creation_flags = creation_flags | CREATE_SUSPENDED; |
| 210 request->startup_info.cb = sizeof(request->startup_info); | 337 request->startup_info.cb = sizeof(request->startup_info); |
| 211 | 338 |
| 212 size_t buffer_offset = sizeof(CreateProcessRequest); | 339 size_t buffer_offset = sizeof(CreateProcessRequest); |
| 213 | 340 |
| 214 request->application_name = reinterpret_cast<LPWSTR>(buffer_offset); | 341 request->application_name = reinterpret_cast<LPWSTR>(buffer_offset); |
| 215 std::copy(application_name.begin(), | 342 std::copy(application_name.begin(), |
| 216 application_name.end(), | 343 application_name.end(), |
| 217 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); | 344 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); |
| 218 buffer_offset += (application_name.size() + 1) * sizeof(wchar_t); | 345 buffer_offset += (application_name.size() + 1) * sizeof(wchar_t); |
| 219 | 346 |
| 220 request->command_line = reinterpret_cast<LPWSTR>(buffer_offset); | 347 request->command_line = reinterpret_cast<LPWSTR>(buffer_offset); |
| 221 std::copy(command_line.begin(), | 348 std::copy(command_line.begin(), |
| 222 command_line.end(), | 349 command_line.end(), |
| 223 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); | 350 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); |
| 224 buffer_offset += (command_line.size() + 1) * sizeof(wchar_t); | 351 buffer_offset += (command_line.size() + 1) * sizeof(wchar_t); |
| 225 | 352 |
| 226 request->startup_info.lpDesktop = | 353 request->startup_info.lpDesktop = |
| 227 reinterpret_cast<LPWSTR>(buffer_offset); | 354 reinterpret_cast<LPWSTR>(buffer_offset); |
| 228 std::copy(desktop_name.begin(), | 355 std::copy(desktop_name.begin(), |
| 229 desktop_name.end(), | 356 desktop_name.end(), |
| 230 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); | 357 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); |
| 231 | 358 |
| 232 // Pass the request to create a process in the target session. | 359 // Pass the request to create a process in the target session. |
| 233 DWORD bytes; | 360 DWORD bytes; |
| 234 if (!WriteFile(pipe, buffer.get(), size, &bytes, NULL)) { | 361 if (!WriteFile(pipe, buffer.get(), size, &bytes, NULL)) { |
| 235 LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request"; | 362 LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request"; |
| 236 return false; | 363 return false; |
| 237 } | 364 } |
| 238 | 365 |
| 239 // Receive the response. | 366 return true; |
| 240 struct CreateProcessResponse { | 367 } |
| 241 DWORD size; | |
| 242 BOOL success; | |
| 243 DWORD last_error; | |
| 244 PROCESS_INFORMATION process_information; | |
| 245 }; | |
| 246 | 368 |
| 247 CreateProcessResponse response; | 369 // Requests the execution server to create a process in the specified session |
| 248 if (!ReadFile(pipe, &response, sizeof(response), &bytes, NULL)) { | 370 // using the default (i.e. Winlogon) token. This routine relies on undocumented |
| 249 LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response"; | 371 // OS functionality and will likely not work on anything but XP or W2K3. |
| 372 bool CreateRemoteSessionProcess( |
| 373 uint32 session_id, |
| 374 const FilePath::StringType& application_name, |
| 375 const CommandLine::StringType& command_line, |
| 376 DWORD creation_flags, |
| 377 PROCESS_INFORMATION* process_information_out) |
| 378 { |
| 379 DCHECK(base::win::GetVersion() == base::win::VERSION_XP); |
| 380 |
| 381 base::win::ScopedHandle pipe; |
| 382 if (!ConnectToExecutionServer(session_id, &pipe)) |
| 383 return false; |
| 384 |
| 385 if (!SendCreateProcessRequest(pipe, application_name, command_line, |
| 386 creation_flags)) { |
| 250 return false; | 387 return false; |
| 251 } | 388 } |
| 252 | 389 |
| 253 // The server sends the data in one chunk so if we didn't received a complete | 390 PROCESS_INFORMATION process_information; |
| 254 // answer something bad happend and there is no point in retrying. | 391 if (!ReceiveCreateProcessResponse(pipe, &process_information)) |
| 255 if (bytes != sizeof(response)) { | 392 return false; |
| 256 SetLastError(ERROR_RECEIVE_PARTIAL); | 393 |
| 394 if (!ProcessCreateProcessResponse(creation_flags, &process_information)) { |
| 395 CloseHandlesAndTerminateProcess(&process_information); |
| 257 return false; | 396 return false; |
| 258 } | 397 } |
| 259 | 398 |
| 260 if (!response.success) { | 399 *process_information_out = process_information; |
| 261 SetLastError(response.last_error); | |
| 262 return false; | |
| 263 } | |
| 264 | |
| 265 // The execution server does not return handles to the created process and | |
| 266 // thread. | |
| 267 if (response.process_information.hProcess == NULL) { | |
| 268 // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of | |
| 269 // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from | |
| 270 // the XP version of the SDK. | |
| 271 DWORD desired_access = | |
| 272 STANDARD_RIGHTS_REQUIRED | | |
| 273 SYNCHRONIZE | | |
| 274 PROCESS_TERMINATE | | |
| 275 PROCESS_CREATE_THREAD | | |
| 276 PROCESS_SET_SESSIONID | | |
| 277 PROCESS_VM_OPERATION | | |
| 278 PROCESS_VM_READ | | |
| 279 PROCESS_VM_WRITE | | |
| 280 PROCESS_DUP_HANDLE | | |
| 281 PROCESS_CREATE_PROCESS | | |
| 282 PROCESS_SET_QUOTA | | |
| 283 PROCESS_SET_INFORMATION | | |
| 284 PROCESS_QUERY_INFORMATION | | |
| 285 PROCESS_SUSPEND_RESUME; | |
| 286 response.process_information.hProcess = | |
| 287 OpenProcess(desired_access, | |
| 288 FALSE, | |
| 289 response.process_information.dwProcessId); | |
| 290 if (!response.process_information.hProcess) { | |
| 291 LOG_GETLASTERROR(ERROR) << "Failed to open process " | |
| 292 << response.process_information.dwProcessId; | |
| 293 return false; | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 *process_information_out = response.process_information; | |
| 298 return true; | 400 return true; |
| 299 } | 401 } |
| 300 | 402 |
| 301 } // namespace | 403 } // namespace |
| 302 | 404 |
| 303 namespace remoting { | 405 namespace remoting { |
| 304 | 406 |
| 305 // Creates a copy of the current process token for the given |session_id| so | 407 // Creates a copy of the current process token for the given |session_id| so |
| 306 // it can be used to launch a process in that session. | 408 // it can be used to launch a process in that session. |
| 307 bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) { | 409 bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) { |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 408 return false; | 510 return false; |
| 409 } | 511 } |
| 410 | 512 |
| 411 CHECK(process_info.IsValid()); | 513 CHECK(process_info.IsValid()); |
| 412 process_out->Set(process_info.TakeProcessHandle()); | 514 process_out->Set(process_info.TakeProcessHandle()); |
| 413 thread_out->Set(process_info.TakeThreadHandle()); | 515 thread_out->Set(process_info.TakeThreadHandle()); |
| 414 return true; | 516 return true; |
| 415 } | 517 } |
| 416 | 518 |
| 417 } // namespace remoting | 519 } // namespace remoting |
| OLD | NEW |