OLD | NEW |
| (Empty) |
1 // Copyright 2006-2009 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 #include "omaha/base/vista_utils.h" | |
17 | |
18 #include <vector> | |
19 #include "base/scoped_ptr.h" | |
20 #include "omaha/base/commontypes.h" | |
21 #include "omaha/base/const_utils.h" | |
22 #include "omaha/base/constants.h" | |
23 #include "omaha/base/debug.h" | |
24 #include "omaha/base/error.h" | |
25 #include "omaha/base/proc_utils.h" | |
26 #include "omaha/base/process.h" | |
27 #include "omaha/base/reg_key.h" | |
28 #include "omaha/base/scope_guard.h" | |
29 #include "omaha/base/scoped_any.h" | |
30 #include "omaha/base/smart_handle.h" | |
31 #include "omaha/base/synchronized.h" | |
32 #include "omaha/base/system.h" | |
33 #include "omaha/base/system_info.h" | |
34 #include "omaha/base/user_info.h" | |
35 #include "omaha/base/user_rights.h" | |
36 #include "omaha/base/utils.h" | |
37 | |
38 #define LOW_INTEGRITY_SDDL_SACL_A NOTRANSL("S:(ML;;NW;;;LW)") | |
39 #define LOW_INTEGRITY_SID_W NOTRANSL(L"S-1-16-4096") | |
40 | |
41 namespace omaha { | |
42 | |
43 namespace vista { | |
44 | |
45 namespace { | |
46 | |
47 // TODO(Omaha): Unit test for this method. | |
48 HRESULT RunAsUser(const CString& command_line, | |
49 HANDLE user_token, | |
50 bool run_as_current_user) { | |
51 if (INVALID_HANDLE_VALUE == user_token) { | |
52 return E_INVALIDARG; | |
53 } | |
54 | |
55 CString cmd(command_line); | |
56 | |
57 STARTUPINFO startup_info = { sizeof(startup_info) }; | |
58 PROCESS_INFORMATION process_info = {0}; | |
59 | |
60 DWORD creation_flags(0); | |
61 void* environment_block(NULL); | |
62 ON_SCOPE_EXIT(::DestroyEnvironmentBlock, environment_block); | |
63 if (::CreateEnvironmentBlock(&environment_block, user_token, FALSE)) { | |
64 creation_flags |= CREATE_UNICODE_ENVIRONMENT; | |
65 } else { | |
66 ASSERT(false, (_T("::CreateEnvironmentBlock failed %d"), ::GetLastError())); | |
67 environment_block = NULL; | |
68 } | |
69 | |
70 // ::CreateProcessAsUser() does not work unless the caller is SYSTEM. Does not | |
71 // matter if the user token is for the current user. | |
72 BOOL success = run_as_current_user ? | |
73 ::CreateProcess(0, CStrBuf(cmd, MAX_PATH), 0, 0, false, creation_flags, | |
74 environment_block, 0, &startup_info, &process_info) : | |
75 ::CreateProcessAsUser(user_token, 0, CStrBuf(cmd, MAX_PATH), 0, 0, false, | |
76 creation_flags, environment_block, 0, &startup_info, | |
77 &process_info); | |
78 | |
79 if (!success) { | |
80 HRESULT hr(HRESULTFromLastError()); | |
81 UTIL_LOG(LE, (_T("[RunAsUser failed][cmd=%s][hresult=0x%x]"), cmd, hr)); | |
82 return hr; | |
83 } | |
84 | |
85 VERIFY1(::CloseHandle(process_info.hThread)); | |
86 VERIFY1(::CloseHandle(process_info.hProcess)); | |
87 | |
88 return S_OK; | |
89 } | |
90 | |
91 } // namespace | |
92 | |
93 bool IsProcessProtected() { | |
94 if (!SystemInfo::IsRunningOnVistaOrLater()) { | |
95 return false; | |
96 } | |
97 | |
98 AutoHandle token; | |
99 VERIFY1(::OpenProcessToken(GetCurrentProcess(), | |
100 TOKEN_QUERY | TOKEN_QUERY_SOURCE, | |
101 &token.receive())); | |
102 | |
103 // Get the Integrity level. | |
104 DWORD length_needed; | |
105 BOOL b = ::GetTokenInformation(token, | |
106 TokenIntegrityLevel, | |
107 NULL, | |
108 0, | |
109 &length_needed); | |
110 ASSERT1(b == FALSE); | |
111 if (b) { | |
112 return false; | |
113 } | |
114 | |
115 // The first call to GetTokenInformation is just to get the buffer size | |
116 if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { | |
117 return false; | |
118 } | |
119 | |
120 scoped_ptr<TOKEN_MANDATORY_LABEL> integration_level; | |
121 | |
122 integration_level.reset(reinterpret_cast<TOKEN_MANDATORY_LABEL*>( | |
123 new char[length_needed])); | |
124 if (integration_level.get() == NULL) { | |
125 return false; | |
126 } | |
127 | |
128 if (!::GetTokenInformation(token, | |
129 TokenIntegrityLevel, | |
130 integration_level.get(), | |
131 length_needed, | |
132 &length_needed)) { | |
133 return false; | |
134 } | |
135 | |
136 wchar_t* sid_str = NULL; | |
137 VERIFY1(::ConvertSidToStringSid(integration_level->Label.Sid, &sid_str)); | |
138 bool ret = ::lstrcmpW(sid_str, LOW_INTEGRITY_SID_W) == 0; | |
139 ::LocalFree(sid_str); | |
140 | |
141 return ret; | |
142 } | |
143 | |
144 HRESULT AllowProtectedProcessAccessToSharedObject(const TCHAR* name) { | |
145 if (!SystemInfo::IsRunningOnVistaOrLater()) { | |
146 return S_FALSE; | |
147 } | |
148 | |
149 ASSERT1(name != NULL); | |
150 | |
151 PSECURITY_DESCRIPTOR psd = NULL; | |
152 VERIFY1(::ConvertStringSecurityDescriptorToSecurityDescriptorA( | |
153 LOW_INTEGRITY_SDDL_SACL_A, | |
154 SDDL_REVISION_1, | |
155 &psd, | |
156 NULL)); | |
157 | |
158 BOOL sacl_present = FALSE; | |
159 BOOL sacl_defaulted = FALSE; | |
160 PACL sacl = NULL; | |
161 VERIFY1(::GetSecurityDescriptorSacl(psd, | |
162 &sacl_present, | |
163 &sacl, | |
164 &sacl_defaulted)); | |
165 | |
166 DWORD ret = ::SetNamedSecurityInfoW(const_cast<TCHAR*>(name), | |
167 SE_KERNEL_OBJECT, | |
168 LABEL_SECURITY_INFORMATION, | |
169 NULL, | |
170 NULL, | |
171 NULL, | |
172 sacl); | |
173 | |
174 ::LocalFree(psd); | |
175 | |
176 return HRESULT_FROM_WIN32(ret); | |
177 } | |
178 | |
179 HRESULT RunAsCurrentUser(const CString& command_line) { | |
180 scoped_handle token; | |
181 if (!::OpenProcessToken(::GetCurrentProcess(), | |
182 TOKEN_QUERY | TOKEN_DUPLICATE, | |
183 address(token))) { | |
184 HRESULT hr = HRESULTFromLastError(); | |
185 UTIL_LOG(LE, (_T("[RunAsCurrentUser: OpenProcessToken failed][0x%x]"), hr)); | |
186 return hr; | |
187 } | |
188 | |
189 return RunAsUser(command_line, get(token), true); | |
190 } | |
191 | |
192 static HRESULT StartInternetExplorerAsUser(HANDLE user_token, | |
193 const CString& options) { | |
194 // Internet Explorer path | |
195 CString ie_file_path; | |
196 HRESULT result = RegKey::GetValue(kRegKeyIeClass, | |
197 kRegValueIeClass, | |
198 &ie_file_path); | |
199 ASSERT1(SUCCEEDED(result)); | |
200 | |
201 if (SUCCEEDED(result)) { | |
202 CString command_line(ie_file_path); | |
203 command_line += _T(' '); | |
204 command_line += options; | |
205 UTIL_LOG(L5, (_T("[StartInternetExplorerAsUser]") | |
206 _T("[Running IExplore with command line][%s]"), | |
207 command_line)); | |
208 result = RunAsUser(command_line, user_token, false); | |
209 } | |
210 return result; | |
211 } | |
212 | |
213 // | |
214 // Constants used by RestartIEUser() | |
215 // | |
216 // The IEUser executable name | |
217 const TCHAR* kIEUser = _T("IEUSER.EXE"); | |
218 | |
219 // The maximum number of simultaneous | |
220 // logged on users in FUS that we support | |
221 const int kMaximumUsers = 16; | |
222 | |
223 | |
224 // Restart IEUser processs. This is to allow for | |
225 // IEUser.exe to refresh it's ElevationPolicy cache. Due to a bug | |
226 // within IE7, IEUser.exe does not refresh it's cache unless it | |
227 // is restarted in the manner below. If the cache is not refreshed | |
228 // IEUser does not respect any new ElevationPolicies that a fresh | |
229 // setup program installs for an ActiveX control or BHO. This code | |
230 // is adapted from Toolbar. | |
231 HRESULT RestartIEUser() { | |
232 // Use the service to restart IEUser. | |
233 // This allows us to restart IEUser for: | |
234 // (a) Multiple users for the first-install case | |
235 // (we currently only restart IEUser for the current interactive user) | |
236 // (b) Even if we are started in an elevated mode | |
237 | |
238 if (!SystemInfo::IsRunningOnVistaOrLater()) { | |
239 UTIL_LOG(L5, (_T("[RestartIEUser - not running on Vista - Exiting]"))); | |
240 return S_OK; | |
241 } | |
242 | |
243 // The restart should be attempted from the system account | |
244 bool is_system_process = false; | |
245 if (FAILED(IsSystemProcess(&is_system_process)) || !is_system_process) { | |
246 ASSERT1(false); | |
247 return E_ACCESSDENIED; | |
248 } | |
249 | |
250 // Get the list of users currently running IEUser.exe processes. | |
251 scoped_handle ieuser_users[kMaximumUsers]; | |
252 int number_of_users = 0; | |
253 Process::GetUsersOfProcesses(kIEUser, kMaximumUsers, ieuser_users, | |
254 &number_of_users); | |
255 | |
256 UTIL_LOG(L5, (_T("[RestartIEUser]") | |
257 _T("[number_of_users running IEUser %d]"), number_of_users)); | |
258 | |
259 if (!number_of_users) { | |
260 UTIL_LOG(L5, (_T("[RestartIEUser][No IEUser processes running]"))); | |
261 return S_OK; | |
262 } | |
263 | |
264 // Kill current IEUser processes. | |
265 ProcessTerminator pt(kIEUser); | |
266 const int kKillWaitTimeoutMs = 5000; | |
267 bool found = false; | |
268 const int kill_method = (ProcessTerminator::KILL_METHOD_4_TERMINATE_PROCESS); | |
269 | |
270 RET_IF_FAILED(pt.KillTheProcess(kKillWaitTimeoutMs, | |
271 &found, | |
272 kill_method, | |
273 false)); | |
274 | |
275 // Restart them. | |
276 HRESULT result = S_OK; | |
277 for (int i = 0; i < number_of_users; i++) { | |
278 // To start a new ieuser.exe, simply start iexplore.exe as a normal user | |
279 // The -embedding prevents IE from opening a window | |
280 HRESULT restart_result = StartInternetExplorerAsUser(get(ieuser_users[i]), | |
281 _T("-embedding")); | |
282 if (FAILED(restart_result)) { | |
283 UTIL_LOG(LEVEL_ERROR, (_T("[StartInternetExplorerAsUser failed][0x%x]"), | |
284 restart_result)); | |
285 result = restart_result; | |
286 } | |
287 } | |
288 | |
289 return result; | |
290 } | |
291 | |
292 HRESULT GetExplorerPidForCurrentUserOrSession(uint32* pid) { | |
293 ASSERT1(pid); | |
294 std::vector<uint32> pids; | |
295 HRESULT hr = GetProcessPidsForActiveUserOrSession(kExplorer, &pids); | |
296 if (FAILED(hr)) { | |
297 CORE_LOG(LW, (_T("[Did not find explorer.exe processes][0x%x]"), hr)); | |
298 return hr; | |
299 } | |
300 | |
301 CORE_LOG(L1, (_T("[Found %u instance(s) of explorer.exe]"), pids.size())); | |
302 | |
303 *pid = pids[0]; // Return only the first instance of explorer.exe. | |
304 return S_OK; | |
305 } | |
306 | |
307 HRESULT GetExplorerTokenForLoggedInUser(HANDLE* token) { | |
308 UTIL_LOG(L3, (_T("[GetExplorerTokenForLoggedInUser]"))); | |
309 ASSERT1(token); | |
310 | |
311 // TODO(omaha): One can set the windows shell to be other than | |
312 // explorer.exe, handle this case. One way to handle this is to | |
313 // read the regkey | |
314 // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon | |
315 // The only problem with this is it can be overriden with the user reg keys. | |
316 // Need to figure out a method to do this. | |
317 // Also consider using the interactive user before picking the first explorer | |
318 // process i.e. the active user. | |
319 std::vector<uint32> processes; | |
320 DWORD flags = EXCLUDE_CURRENT_PROCESS; | |
321 std::vector<CString> command_lines; | |
322 CString explorer_file_name(kExplorer); | |
323 CString user_sid; | |
324 | |
325 HRESULT hr = Process::FindProcesses(flags, | |
326 explorer_file_name, | |
327 true, | |
328 user_sid, | |
329 command_lines, | |
330 &processes); | |
331 if (FAILED(hr)) { | |
332 CORE_LOG(LEVEL_ERROR, (_T("[FindProcesses failed][0x%08x]"), hr)); | |
333 return hr; | |
334 } | |
335 | |
336 std::vector<uint32>::const_iterator iter = processes.begin(); | |
337 for (; iter != processes.end(); ++iter) { | |
338 uint32 explorer_pid = *iter; | |
339 scoped_handle exp(::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, | |
340 false, | |
341 explorer_pid)); | |
342 if (exp) { | |
343 if (::OpenProcessToken(get(exp), | |
344 TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_IMPERSONATE, | |
345 token)) { | |
346 // TODO(omaha): Consider using the GetWindowsAccountDomainSid | |
347 // method here. This method returns the domain SID associated | |
348 // with the passed in SID. This allows us to detect if the user is a | |
349 // domain user. We should prefer domain users over normal users, | |
350 // as in corporate environments, these users will be more likely to | |
351 // allow to be tunneled through a proxy. | |
352 return S_OK; | |
353 } else { | |
354 hr = HRESULTFromLastError(); | |
355 CORE_LOG(LEVEL_WARNING, (_T("[OpenProcessToken failed][0x%08x]"), hr)); | |
356 } | |
357 } else { | |
358 hr = HRESULTFromLastError(); | |
359 CORE_LOG(LEVEL_WARNING, (_T("[OpenProcess failed][0x%08x]"), hr)); | |
360 } | |
361 } | |
362 | |
363 return hr; | |
364 } | |
365 | |
366 HRESULT GetPidsInSession(const TCHAR* exe_name, | |
367 const TCHAR* user_sid, | |
368 DWORD session_id, | |
369 std::vector<uint32>* pids) { | |
370 ASSERT1(pids); | |
371 ASSERT1(exe_name); | |
372 ASSERT1(*exe_name); | |
373 UTIL_LOG(L3, (_T("[GetPidsInSession][%s][sid=%s][session=%d]"), | |
374 exe_name, user_sid, session_id)); | |
375 | |
376 pids->clear(); | |
377 | |
378 DWORD flags = EXCLUDE_CURRENT_PROCESS; | |
379 if (user_sid != NULL) { | |
380 flags |= INCLUDE_ONLY_PROCESS_OWNED_BY_USER; | |
381 } | |
382 std::vector<CString> command_lines; | |
383 HRESULT hr = Process::FindProcessesInSession(session_id, | |
384 flags, | |
385 exe_name, | |
386 true, | |
387 user_sid, | |
388 command_lines, | |
389 pids); | |
390 if (FAILED(hr)) { | |
391 return hr; | |
392 } | |
393 return pids->empty() ? HRESULT_FROM_WIN32(ERROR_NOT_FOUND) : S_OK; | |
394 } | |
395 | |
396 HRESULT GetProcessPidsForActiveUserOrSession(const TCHAR* exe_name, | |
397 std::vector<uint32>* pids) { | |
398 bool is_system = false; | |
399 HRESULT hr = IsSystemProcess(&is_system); | |
400 if (FAILED(hr)) { | |
401 NET_LOG(LE, (_T("[IsSystemProcess failed][0x%x]"), hr)); | |
402 return hr; | |
403 } | |
404 | |
405 if (is_system) { | |
406 return vista::GetPidsInSession(exe_name, | |
407 NULL, | |
408 System::GetActiveSessionId(), | |
409 pids); | |
410 } | |
411 | |
412 CString user_sid; | |
413 // If this call fails, we are still ok. | |
414 omaha::user_info::GetProcessUser(NULL, NULL, &user_sid); | |
415 DWORD current_session = System::GetCurrentSessionId(); | |
416 if (FAILED(vista::GetPidsInSession(exe_name, | |
417 user_sid, | |
418 current_session, | |
419 pids))) { | |
420 // In the case of RunAs, the processes may be under a different identity | |
421 // than the current sid. So if we are unable to find a process under the | |
422 // current user's sid, we search for processes running in the current | |
423 // session regardless of the sid they are running under. | |
424 return vista::GetPidsInSession(exe_name, | |
425 NULL, | |
426 current_session, | |
427 pids); | |
428 } | |
429 | |
430 return S_OK; | |
431 } | |
432 | |
433 | |
434 | |
435 HRESULT StartProcessWithTokenOfProcess(uint32 pid, | |
436 const CString& command_line) { | |
437 UTIL_LOG(L5, (_T("[StartProcessWithTokenOfProcess]") | |
438 _T("[pid %u][command_line '%s']"), pid, command_line)); | |
439 | |
440 // Get the token from process. | |
441 scoped_handle user_token; | |
442 HRESULT hr = Process::GetImpersonationToken(pid, address(user_token)); | |
443 if (FAILED(hr)) { | |
444 CORE_LOG(LEVEL_ERROR, (_T("[GetImpersonationToken failed][0x%08x]"), hr)); | |
445 return hr; | |
446 } | |
447 | |
448 // Start process using the token. | |
449 UTIL_LOG(L5, (_T("[StartProcessWithTokenOfProcess][Running process %s]"), | |
450 command_line)); | |
451 hr = RunAsUser(command_line, get(user_token), false); | |
452 | |
453 if (FAILED(hr)) { | |
454 UTIL_LOG(LEVEL_ERROR, | |
455 (_T("[Vista::StartProcessWithTokenOfProcess - RunAsUser failed][0x%x]"), | |
456 hr)); | |
457 } | |
458 | |
459 return hr; | |
460 } | |
461 | |
462 HRESULT GetLoggedOnUserToken(HANDLE* token) { | |
463 ASSERT1(token); | |
464 *token = NULL; | |
465 | |
466 uint32 pid = 0; | |
467 HRESULT hr = GetExplorerPidForCurrentUserOrSession(&pid); | |
468 if (FAILED(hr)) { | |
469 return hr; | |
470 } | |
471 hr = Process::GetImpersonationToken(pid, token); | |
472 if (FAILED(hr)) { | |
473 return hr; | |
474 } | |
475 | |
476 ASSERT1(*token); | |
477 return S_OK; | |
478 } | |
479 | |
480 } // namespace vista | |
481 | |
482 } // namespace omaha | |
483 | |
OLD | NEW |