blob: 5e0a6949fc64083ea36e3a2878b5930ee715b538 [file] [log] [blame]
// Copyright 2006-2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
#include "omaha/common/vista_utils.h"
#include <vector>
#include "base/scoped_ptr.h"
#include "omaha/common/const_utils.h"
#include "omaha/common/constants.h"
#include "omaha/common/debug.h"
#include "omaha/common/error.h"
#include "omaha/common/proc_utils.h"
#include "omaha/common/process.h"
#include "omaha/common/reg_key.h"
#include "omaha/common/scope_guard.h"
#include "omaha/common/scoped_any.h"
#include "omaha/common/smart_handle.h"
#include "omaha/common/synchronized.h"
#include "omaha/common/system.h"
#include "omaha/common/system_info.h"
#include "omaha/common/user_info.h"
#include "omaha/common/user_rights.h"
#include "omaha/common/utils.h"
#define LOW_INTEGRITY_SDDL_SACL_A NOTRANSL("S:(ML;;NW;;;LW)")
#define LOW_INTEGRITY_SID_W NOTRANSL(L"S-1-16-4096")
namespace omaha {
namespace vista {
namespace {
// TODO(Omaha): Unit test for this method.
HRESULT RunAsUser(const CString& command_line,
HANDLE user_token,
bool run_as_current_user) {
if (INVALID_HANDLE_VALUE == user_token) {
return E_INVALIDARG;
}
CString cmd(command_line);
STARTUPINFO startup_info = { sizeof(startup_info) };
PROCESS_INFORMATION process_info = {0};
DWORD creation_flags(0);
void* environment_block(NULL);
ON_SCOPE_EXIT(::DestroyEnvironmentBlock, environment_block);
if (::CreateEnvironmentBlock(&environment_block, user_token, FALSE)) {
creation_flags |= CREATE_UNICODE_ENVIRONMENT;
} else {
ASSERT(false, (_T("::CreateEnvironmentBlock failed %d"), ::GetLastError()));
environment_block = NULL;
}
// ::CreateProcessAsUser() does not work unless the caller is SYSTEM. Does not
// matter if the user token is for the current user.
BOOL success = run_as_current_user ?
::CreateProcess(0, CStrBuf(cmd, MAX_PATH), 0, 0, false, creation_flags,
environment_block, 0, &startup_info, &process_info) :
::CreateProcessAsUser(user_token, 0, CStrBuf(cmd, MAX_PATH), 0, 0, false,
creation_flags, environment_block, 0, &startup_info,
&process_info);
if (!success) {
HRESULT hr(HRESULTFromLastError());
UTIL_LOG(LE, (_T("[RunAsUser failed][cmd=%s][hresult=0x%x]"), cmd, hr));
return hr;
}
VERIFY1(::CloseHandle(process_info.hThread));
VERIFY1(::CloseHandle(process_info.hProcess));
return S_OK;
}
} // namespace
bool IsProcessProtected() {
if (!SystemInfo::IsRunningOnVistaOrLater()) {
return false;
}
AutoHandle token;
VERIFY1(::OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY | TOKEN_QUERY_SOURCE,
&token.receive()));
// Get the Integrity level.
DWORD length_needed;
BOOL b = ::GetTokenInformation(token,
TokenIntegrityLevel,
NULL,
0,
&length_needed);
ASSERT1(b == FALSE);
if (b) {
return false;
}
// The first call to GetTokenInformation is just to get the buffer size
if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
return false;
}
scoped_ptr<TOKEN_MANDATORY_LABEL> integration_level;
integration_level.reset(reinterpret_cast<TOKEN_MANDATORY_LABEL*>(
new char[length_needed]));
if (integration_level.get() == NULL) {
return false;
}
if (!::GetTokenInformation(token,
TokenIntegrityLevel,
integration_level.get(),
length_needed,
&length_needed)) {
return false;
}
wchar_t* sid_str = NULL;
VERIFY1(::ConvertSidToStringSid(integration_level->Label.Sid, &sid_str));
bool ret = ::lstrcmpW(sid_str, LOW_INTEGRITY_SID_W) == 0;
::LocalFree(sid_str);
return ret;
}
HRESULT AllowProtectedProcessAccessToSharedObject(const TCHAR* name) {
if (!SystemInfo::IsRunningOnVistaOrLater()) {
return S_FALSE;
}
ASSERT1(name != NULL);
PSECURITY_DESCRIPTOR psd = NULL;
VERIFY1(::ConvertStringSecurityDescriptorToSecurityDescriptorA(
LOW_INTEGRITY_SDDL_SACL_A,
SDDL_REVISION_1,
&psd,
NULL));
BOOL sacl_present = FALSE;
BOOL sacl_defaulted = FALSE;
PACL sacl = NULL;
VERIFY1(::GetSecurityDescriptorSacl(psd,
&sacl_present,
&sacl,
&sacl_defaulted));
DWORD ret = ::SetNamedSecurityInfoW(const_cast<TCHAR*>(name),
SE_KERNEL_OBJECT,
LABEL_SECURITY_INFORMATION,
NULL,
NULL,
NULL,
sacl);
::LocalFree(psd);
return HRESULT_FROM_WIN32(ret);
}
HRESULT RunAsCurrentUser(const CString& command_line) {
scoped_handle token;
if (!::OpenProcessToken(::GetCurrentProcess(),
TOKEN_QUERY | TOKEN_DUPLICATE,
address(token))) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LE, (_T("[RunAsCurrentUser: OpenProcessToken failed][0x%x]"), hr));
return hr;
}
return RunAsUser(command_line, get(token), true);
}
static HRESULT StartInternetExplorerAsUser(HANDLE user_token,
const CString& options) {
// Internet Explorer path
CString ie_file_path;
HRESULT result = RegKey::GetValue(kRegKeyIeClass,
kRegValueIeClass,
&ie_file_path);
ASSERT1(SUCCEEDED(result));
if (SUCCEEDED(result)) {
CString command_line(ie_file_path);
command_line += _T(' ');
command_line += options;
UTIL_LOG(L5, (_T("[StartInternetExplorerAsUser]")
_T("[Running IExplore with command line][%s]"),
command_line));
result = RunAsUser(command_line, user_token, false);
}
return result;
}
//
// Constants used by RestartIEUser()
//
// The IEUser executable name
const TCHAR* kIEUser = _T("IEUSER.EXE");
// The maximum number of simultaneous
// logged on users in FUS that we support
const int kMaximumUsers = 16;
// Restart IEUser processs. This is to allow for
// IEUser.exe to refresh it's ElevationPolicy cache. Due to a bug
// within IE7, IEUser.exe does not refresh it's cache unless it
// is restarted in the manner below. If the cache is not refreshed
// IEUser does not respect any new ElevationPolicies that a fresh
// setup program installs for an ActiveX control or BHO. This code
// is adapted from Toolbar.
HRESULT RestartIEUser() {
// Use the service to restart IEUser.
// This allows us to restart IEUser for:
// (a) Multiple users for the first-install case
// (we currently only restart IEUser for the current interactive user)
// (b) Even if we are started in an elevated mode
if (!SystemInfo::IsRunningOnVistaOrLater()) {
UTIL_LOG(L5, (_T("[RestartIEUser - not running on Vista - Exiting]")));
return S_OK;
}
// The restart should be attempted from the system account
bool is_system_process = false;
if (FAILED(IsSystemProcess(&is_system_process)) || !is_system_process) {
ASSERT1(false);
return E_ACCESSDENIED;
}
// Get the list of users currently running IEUser.exe processes.
scoped_handle ieuser_users[kMaximumUsers];
int number_of_users = 0;
Process::GetUsersOfProcesses(kIEUser, kMaximumUsers, ieuser_users,
&number_of_users);
UTIL_LOG(L5, (_T("[RestartIEUser]")
_T("[number_of_users running IEUser %d]"), number_of_users));
if (!number_of_users) {
UTIL_LOG(L5, (_T("[RestartIEUser][No IEUser processes running]")));
return S_OK;
}
// Kill current IEUser processes.
ProcessTerminator pt(kIEUser);
const int kKillWaitTimeoutMs = 5000;
bool found = false;
const int kill_method = (ProcessTerminator::KILL_METHOD_4_TERMINATE_PROCESS);
RET_IF_FAILED(pt.KillTheProcess(kKillWaitTimeoutMs,
&found,
kill_method,
false));
// Restart them.
HRESULT result = S_OK;
for (int i = 0; i < number_of_users; i++) {
// To start a new ieuser.exe, simply start iexplore.exe as a normal user
// The -embedding prevents IE from opening a window
HRESULT restart_result = StartInternetExplorerAsUser(get(ieuser_users[i]),
_T("-embedding"));
if (FAILED(restart_result)) {
UTIL_LOG(LEVEL_ERROR, (_T("[StartInternetExplorerAsUser failed][0x%x]"),
restart_result));
result = restart_result;
}
}
return result;
}
HRESULT GetExplorerPidForCurrentUserOrSession(uint32* pid) {
ASSERT1(pid);
std::vector<uint32> pids;
HRESULT hr = GetProcessPidsForActiveUserOrSession(kExplorer, &pids);
if (FAILED(hr)) {
CORE_LOG(LW, (_T("[Did not find explorer.exe processes][0x%x]"), hr));
return hr;
}
CORE_LOG(L1, (_T("[Found %u instance(s) of explorer.exe]"), pids.size()));
*pid = pids[0]; // Return only the first instance of explorer.exe.
return S_OK;
}
HRESULT GetExplorerTokenForLoggedInUser(HANDLE* token) {
UTIL_LOG(L3, (_T("[GetExplorerTokenForLoggedInUser]")));
ASSERT1(token);
// TODO(omaha): One can set the windows shell to be other than
// explorer.exe, handle this case. One way to handle this is to
// read the regkey
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
// The only problem with this is it can be overriden with the user reg keys.
// Need to figure out a method to do this.
// Also consider using the interactive user before picking the first explorer
// process i.e. the active user.
std::vector<uint32> processes;
DWORD flags = EXCLUDE_CURRENT_PROCESS;
std::vector<CString> command_lines;
CString explorer_file_name(kExplorer);
CString user_sid;
HRESULT hr = Process::FindProcesses(flags,
explorer_file_name,
true,
user_sid,
command_lines,
&processes);
if (FAILED(hr)) {
CORE_LOG(LEVEL_ERROR, (_T("[FindProcesses failed][0x%08x]"), hr));
return hr;
}
std::vector<uint32>::const_iterator iter = processes.begin();
for (; iter != processes.end(); ++iter) {
uint32 explorer_pid = *iter;
scoped_handle exp(::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
false,
explorer_pid));
if (exp) {
if (::OpenProcessToken(get(exp),
TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_IMPERSONATE,
token)) {
// TODO(omaha): Consider using the GetWindowsAccountDomainSid
// method here. This method returns the domain SID associated
// with the passed in SID. This allows us to detect if the user is a
// domain user. We should prefer domain users over normal users,
// as in corporate environments, these users will be more likely to
// allow to be tunneled through a proxy.
return S_OK;
} else {
hr = HRESULTFromLastError();
CORE_LOG(LEVEL_WARNING, (_T("[OpenProcessToken failed][0x%08x]"), hr));
}
} else {
hr = HRESULTFromLastError();
CORE_LOG(LEVEL_WARNING, (_T("[OpenProcess failed][0x%08x]"), hr));
}
}
return hr;
}
HRESULT GetPidsInSession(const TCHAR* exe_name,
const TCHAR* user_sid,
DWORD session_id,
std::vector<uint32>* pids) {
ASSERT1(pids);
ASSERT1(exe_name);
ASSERT1(*exe_name);
UTIL_LOG(L3, (_T("[GetPidsInSession][%s][sid=%s][session=%d]"),
exe_name, user_sid, session_id));
pids->clear();
DWORD flags = EXCLUDE_CURRENT_PROCESS;
if (user_sid != NULL) {
flags |= INCLUDE_ONLY_PROCESS_OWNED_BY_USER;
}
std::vector<CString> command_lines;
HRESULT hr = Process::FindProcessesInSession(session_id,
flags,
exe_name,
true,
user_sid,
command_lines,
pids);
if (FAILED(hr)) {
return hr;
}
return pids->empty() ? HRESULT_FROM_WIN32(ERROR_NOT_FOUND) : S_OK;
}
HRESULT GetProcessPidsForActiveUserOrSession(const TCHAR* exe_name,
std::vector<uint32>* pids) {
bool is_system = false;
HRESULT hr = IsSystemProcess(&is_system);
if (FAILED(hr)) {
NET_LOG(LE, (_T("[IsSystemProcess failed][0x%x]"), hr));
return hr;
}
if (is_system) {
return vista::GetPidsInSession(exe_name,
NULL,
System::GetActiveSessionId(),
pids);
}
CString user_sid;
// If this call fails, we are still ok.
omaha::user_info::GetCurrentUser(NULL, NULL, &user_sid);
DWORD current_session = System::GetCurrentSessionId();
if (FAILED(vista::GetPidsInSession(exe_name,
user_sid,
current_session,
pids))) {
// In the case of RunAs, the processes may be under a different identity
// than the current sid. So if we are unable to find a process under the
// current user's sid, we search for processes running in the current
// session regardless of the sid they are running under.
return vista::GetPidsInSession(exe_name,
NULL,
current_session,
pids);
}
return S_OK;
}
HRESULT StartProcessWithTokenOfProcess(uint32 pid,
const CString& command_line) {
UTIL_LOG(L5, (_T("[StartProcessWithTokenOfProcess]")
_T("[pid %u][command_line '%s']"), pid, command_line));
// Get the token from process.
scoped_handle user_token;
HRESULT hr = Process::GetImpersonationToken(pid, address(user_token));
if (FAILED(hr)) {
CORE_LOG(LEVEL_ERROR, (_T("[GetImpersonationToken failed][0x%08x]"), hr));
return hr;
}
// Start process using the token.
UTIL_LOG(L5, (_T("[StartProcessWithTokenOfProcess][Running process %s]"),
command_line));
hr = RunAsUser(command_line, get(user_token), false);
if (FAILED(hr)) {
UTIL_LOG(LEVEL_ERROR,
(_T("[Vista::StartProcessWithTokenOfProcess - RunAsUser failed][0x%x]"),
hr));
}
return hr;
}
HRESULT GetLoggedOnUserToken(HANDLE* token) {
ASSERT1(token);
*token = NULL;
uint32 pid = 0;
HRESULT hr = GetExplorerPidForCurrentUserOrSession(&pid);
if (FAILED(hr)) {
return hr;
}
hr = Process::GetImpersonationToken(pid, token);
if (FAILED(hr)) {
return hr;
}
ASSERT1(*token);
return S_OK;
}
} // namespace vista
} // namespace omaha