OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "remoting/host/launch_process_in_session_win.h" | |
6 | |
7 #include <windows.h> | |
8 #include <winternl.h> | |
9 | |
10 #include "base/logging.h" | |
11 #include "base/memory/scoped_ptr.h" | |
12 #include "base/scoped_native_library.h" | |
13 #include "base/stringprintf.h" | |
14 #include "base/utf_string_conversions.h" | |
15 #include "base/win/scoped_handle.h" | |
16 #include "base/win/scoped_process_information.h" | |
17 #include "base/win/windows_version.h" | |
18 | |
19 using base::win::ScopedHandle; | |
20 | |
21 namespace { | |
22 | |
23 const wchar_t kCreateProcessDefaultPipeNameFormat[] = | |
24 L"\\\\.\\Pipe\\TerminalServer\\SystemExecSrvr\\%d"; | |
25 | |
26 // Undocumented WINSTATIONINFOCLASS value causing | |
27 // winsta!WinStationQueryInformationW() to return the name of the pipe for | |
28 // requesting cross-session process creation. | |
29 const WINSTATIONINFOCLASS kCreateProcessPipeNameClass = | |
30 static_cast<WINSTATIONINFOCLASS>(0x21); | |
31 | |
32 const int kPipeBusyWaitTimeoutMs = 2000; | |
33 const int kPipeConnectMaxAttempts = 3; | |
34 | |
35 // The minimum and maximum delays between attempts to inject host process into | |
36 // a session. | |
37 const int kMaxLaunchDelaySeconds = 60; | |
38 const int kMinLaunchDelaySeconds = 1; | |
39 | |
40 // Name of the default session desktop. | |
41 wchar_t kDefaultDesktopName[] = L"winsta0\\default"; | |
42 | |
43 // Requests the execution server to create a process in the specified session | |
44 // using the default (i.e. Winlogon) token. This routine relies on undocumented | |
45 // OS functionality and will likely not work on anything but XP or W2K3. | |
46 bool CreateRemoteSessionProcess( | |
47 uint32 session_id, | |
48 const std::wstring& application_name, | |
49 const std::wstring& command_line, | |
50 PROCESS_INFORMATION* process_information_out) | |
51 { | |
52 DCHECK(base::win::GetVersion() == base::win::VERSION_XP); | |
53 | |
54 std::wstring pipe_name; | |
55 | |
56 // Use winsta!WinStationQueryInformationW() to determine the process creation | |
57 // pipe name for the session. | |
58 FilePath winsta_path(base::GetNativeLibraryName(UTF8ToUTF16("winsta"))); | |
59 base::ScopedNativeLibrary winsta(winsta_path); | |
60 if (winsta.is_valid()) { | |
61 PWINSTATIONQUERYINFORMATIONW win_station_query_information = | |
62 static_cast<PWINSTATIONQUERYINFORMATIONW>( | |
63 winsta.GetFunctionPointer("WinStationQueryInformationW")); | |
64 if (win_station_query_information) { | |
65 wchar_t name[MAX_PATH]; | |
66 ULONG name_length; | |
67 if (win_station_query_information(0, | |
68 session_id, | |
69 kCreateProcessPipeNameClass, | |
70 name, | |
71 sizeof(name), | |
72 &name_length)) { | |
73 pipe_name.assign(name); | |
74 } | |
75 } | |
76 } | |
77 | |
78 // Use the default pipe name if we couldn't query its name. | |
79 if (pipe_name.empty()) { | |
80 pipe_name = StringPrintf(kCreateProcessDefaultPipeNameFormat, session_id); | |
81 } | |
82 | |
83 // Try to connect to the named pipe. | |
84 base::win::ScopedHandle pipe; | |
85 for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { | |
86 pipe.Set(CreateFile(pipe_name.c_str(), | |
87 GENERIC_READ | GENERIC_WRITE, | |
88 0, | |
89 NULL, | |
90 OPEN_EXISTING, | |
91 0, | |
92 NULL)); | |
93 if (pipe.IsValid()) { | |
94 break; | |
95 } | |
96 | |
97 // Cannot continue retrying if error is something other than | |
98 // ERROR_PIPE_BUSY. | |
99 if (GetLastError() != ERROR_PIPE_BUSY) { | |
100 break; | |
101 } | |
102 | |
103 // Cannot continue retrying if wait on pipe fails. | |
104 if (!WaitNamedPipe(pipe_name.c_str(), kPipeBusyWaitTimeoutMs)) { | |
105 break; | |
106 } | |
107 } | |
108 | |
109 if (!pipe.IsValid()) { | |
110 LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'"; | |
111 return false; | |
112 } | |
113 | |
114 std::wstring desktop_name(kDefaultDesktopName); | |
115 | |
116 // |CreateProcessRequest| structure passes the same parameters to | |
117 // the execution server as CreateProcessAsUser() function does. Strings are | |
118 // stored as wide strings immediately after the structure. String pointers are | |
119 // represented as byte offsets to string data from the beginning of | |
120 // the structure. | |
121 struct CreateProcessRequest { | |
122 DWORD size; | |
123 DWORD process_id; | |
124 BOOL use_default_token; | |
125 HANDLE token; | |
126 LPWSTR application_name; | |
127 LPWSTR command_line; | |
128 SECURITY_ATTRIBUTES process_attributes; | |
129 SECURITY_ATTRIBUTES thread_attributes; | |
130 BOOL inherit_handles; | |
131 DWORD creation_flags; | |
132 LPVOID environment; | |
133 LPWSTR current_directory; | |
134 STARTUPINFOW startup_info; | |
135 PROCESS_INFORMATION process_information; | |
136 }; | |
137 | |
138 // Allocate a large enough buffer to hold the CreateProcessRequest structure | |
139 // and three NULL-terminated string parameters. | |
140 size_t size = sizeof(CreateProcessRequest) + sizeof(wchar_t) * | |
141 (application_name.size() + command_line.size() + desktop_name.size() + 3); | |
142 scoped_array<char> buffer(new char[size]); | |
143 memset(buffer.get(), 0, size); | |
144 | |
145 // Marshal the input parameters. | |
146 CreateProcessRequest* request = | |
147 reinterpret_cast<CreateProcessRequest*>(buffer.get()); | |
148 request->size = size; | |
149 request->use_default_token = TRUE; | |
150 request->process_id = GetCurrentProcessId(); | |
151 request->startup_info.cb = sizeof(request->startup_info); | |
152 | |
153 size_t buffer_offset = sizeof(CreateProcessRequest); | |
154 | |
155 request->application_name = reinterpret_cast<LPWSTR>(buffer_offset); | |
156 std::copy(application_name.begin(), | |
157 application_name.end(), | |
158 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); | |
159 buffer_offset += (application_name.size() + 1) * sizeof(wchar_t); | |
160 | |
161 request->command_line = reinterpret_cast<LPWSTR>(buffer_offset); | |
162 std::copy(command_line.begin(), | |
163 command_line.end(), | |
164 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); | |
165 buffer_offset += (command_line.size() + 1) * sizeof(wchar_t); | |
166 | |
167 request->startup_info.lpDesktop = | |
168 reinterpret_cast<LPWSTR>(buffer_offset); | |
169 std::copy(desktop_name.begin(), | |
170 desktop_name.end(), | |
171 reinterpret_cast<wchar_t*>(buffer.get() + buffer_offset)); | |
172 | |
173 // Pass the request to create a process in the target session. | |
174 DWORD bytes; | |
175 if (!WriteFile(pipe.Get(), buffer.get(), size, &bytes, NULL)) { | |
176 LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request"; | |
177 return false; | |
178 } | |
179 | |
180 // Receive the response. | |
181 struct CreateProcessResponse { | |
182 DWORD size; | |
183 BOOL success; | |
184 DWORD last_error; | |
185 PROCESS_INFORMATION process_information; | |
186 }; | |
187 | |
188 CreateProcessResponse response; | |
189 if (!ReadFile(pipe.Get(), &response, sizeof(response), &bytes, NULL)) { | |
190 LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response"; | |
191 return false; | |
192 } | |
193 | |
194 // The server sends the data in one chunk so if we didn't received a complete | |
195 // answer something bad happend and there is no point in retrying. | |
196 if (bytes != sizeof(response)) { | |
197 SetLastError(ERROR_RECEIVE_PARTIAL); | |
198 return false; | |
199 } | |
200 | |
201 if (!response.success) { | |
202 SetLastError(response.last_error); | |
203 return false; | |
204 } | |
205 | |
206 // The execution server does not return handles to the created process and | |
207 // thread. | |
208 if (response.process_information.hProcess == NULL) { | |
209 // N.B. PROCESS_ALL_ACCESS is different in XP and Vista+ versions of | |
210 // the SDK. |desired_access| below is effectively PROCESS_ALL_ACCESS from | |
211 // the XP version of the SDK. | |
212 DWORD desired_access = | |
213 STANDARD_RIGHTS_REQUIRED | | |
214 SYNCHRONIZE | | |
215 PROCESS_TERMINATE | | |
216 PROCESS_CREATE_THREAD | | |
217 PROCESS_SET_SESSIONID | | |
218 PROCESS_VM_OPERATION | | |
219 PROCESS_VM_READ | | |
220 PROCESS_VM_WRITE | | |
221 PROCESS_DUP_HANDLE | | |
222 PROCESS_CREATE_PROCESS | | |
223 PROCESS_SET_QUOTA | | |
224 PROCESS_SET_INFORMATION | | |
225 PROCESS_QUERY_INFORMATION | | |
226 PROCESS_SUSPEND_RESUME; | |
227 response.process_information.hProcess = | |
228 OpenProcess(desired_access, | |
229 FALSE, | |
230 response.process_information.dwProcessId); | |
231 if (!response.process_information.hProcess) { | |
232 LOG_GETLASTERROR(ERROR) << "Failed to open process " | |
233 << response.process_information.dwProcessId; | |
234 return false; | |
235 } | |
236 } | |
237 | |
238 *process_information_out = response.process_information; | |
239 return true; | |
240 } | |
241 | |
242 } // namespace | |
243 | |
244 namespace remoting { | |
245 | |
246 // Launches |binary| in a different session. The target session is specified by | |
247 // |user_token|. | |
248 bool LaunchProcessInSession(const FilePath& binary, | |
249 const std::wstring& command_line, | |
250 HANDLE user_token, | |
251 base::Process* process_out) { | |
252 std::wstring application_name = binary.value(); | |
253 | |
254 base::win::ScopedProcessInformation process_info; | |
255 STARTUPINFOW startup_info; | |
256 | |
257 memset(&startup_info, 0, sizeof(startup_info)); | |
258 startup_info.cb = sizeof(startup_info); | |
259 startup_info.lpDesktop = kDefaultDesktopName; | |
260 | |
261 BOOL result = CreateProcessAsUser(user_token, | |
262 application_name.c_str(), | |
263 const_cast<LPWSTR>(command_line.c_str()), | |
264 NULL, | |
265 NULL, | |
266 FALSE, | |
267 0, | |
268 NULL, | |
269 NULL, | |
270 &startup_info, | |
271 process_info.Receive()); | |
272 | |
273 // CreateProcessAsUser will fail on XP and W2K3 with ERROR_PIPE_NOT_CONNECTED | |
274 // if the user hasn't logged to the target session yet. In such a case | |
275 // we try to talk to the execution server directly emulating what | |
276 // the undocumented and not-exported advapi32!CreateRemoteSessionProcessW() | |
277 // function does. The created process will run under Winlogon'a token instead | |
278 // of |user_token|. Since Winlogon runs as SYSTEM, this suits our needs. | |
279 if (!result && | |
280 GetLastError() == ERROR_PIPE_NOT_CONNECTED && | |
281 base::win::GetVersion() == base::win::VERSION_XP) { | |
282 DWORD session_id; | |
283 DWORD return_length; | |
284 result = GetTokenInformation(user_token, | |
285 TokenSessionId, | |
286 &session_id, | |
287 sizeof(session_id), | |
288 &return_length); | |
289 if (result && session_id != 0) { | |
290 result = CreateRemoteSessionProcess(session_id, | |
291 application_name, | |
292 command_line, | |
293 process_info.Receive()); | |
294 } else { | |
295 // Restore the error status returned by CreateProcessAsUser(). | |
296 result = FALSE; | |
297 SetLastError(ERROR_PIPE_NOT_CONNECTED); | |
298 } | |
299 } | |
300 | |
301 if (!result) { | |
302 LOG_GETLASTERROR(ERROR) << | |
303 "Failed to launch a process with a user token"; | |
304 return false; | |
305 } | |
306 | |
307 CHECK(process_info.IsValid()); | |
308 process_out->set_handle(process_info.TakeProcessHandle()); | |
309 return true; | |
310 } | |
311 | |
312 } // namespace remoting | |
OLD | NEW |