| // Copyright 2007-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. |
| // ======================================================================== |
| |
| // This is a small shell that loads a DLL calls its well known entry point. |
| // The intention is to only depend on OS mechanisms and avoid the LIBC |
| // dependency completely. |
| // This indirection is done primarily to: |
| // Play nicely with software firewalls. Most firewalls watch the executable |
| // module making network requests and we do not want them to notify the user |
| // after we've updated the program. The DLL can change independently of the |
| // shell and we expect the shell to remain unchanged most of the time. |
| // |
| // Changes to this executable will not appear in offical builds until they are |
| // included in an offical build and the resulting file is checked in to the |
| // saved shell location. |
| |
| // Disable the RTC checks because this shell doesn't build with a CRT. |
| #pragma runtime_checks("", off) |
| |
| #ifndef WIN32_LEAN_AND_MEAN |
| #define WIN32_LEAN_AND_MEAN |
| #endif |
| |
| #include <windows.h> |
| #include <shlobj.h> |
| #include <shlwapi.h> |
| #include <tchar.h> |
| |
| #include <atlbase.h> |
| #include <atlpath.h> |
| #include <atlstr.h> |
| |
| #include "omaha/common/constants.h" |
| #include "omaha/common/error.h" |
| #include "omaha/common/signaturevalidator.h" |
| #include "omaha/goopdate/const_goopdate.h" |
| #include "omaha/goopdate/main.h" |
| |
| namespace omaha { |
| |
| // Disable the stack checks to keep the code size down. |
| // The check_stack pragma did not really work. The stack checks had to be |
| // disabled in the mk_file. |
| #pragma check_stack() |
| |
| // Have to define this here since this is used in signaturevalidator.cc. |
| // This is defined in error.cc, but that pulls in debug.cc, which has a lot |
| // of additional dependencies we do not want. Not worth it for just this |
| // function. |
| HRESULT HRESULTFromLastError() { |
| DWORD error_code = ::GetLastError(); |
| return (error_code != NO_ERROR) ? HRESULT_FROM_WIN32(error_code) : E_FAIL; |
| } |
| |
| // Adapted from File::Exists in file.cc. |
| bool FileExists(const TCHAR* file_name) { |
| if (!file_name || !*file_name) { |
| return false; |
| } |
| |
| WIN32_FILE_ATTRIBUTE_DATA attrs = {0}; |
| return 0 != ::GetFileAttributesEx(file_name, ::GetFileExInfoStandard, &attrs); |
| } |
| |
| // Adapted from vistautil.cc. |
| bool IsVistaOrLater() { |
| static bool known = false; |
| static bool is_vista = false; |
| if (!known) { |
| OSVERSIONINFOEX osvi = {0}; |
| osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); |
| osvi.dwMajorVersion = 6; |
| DWORDLONG conditional = 0; |
| VER_SET_CONDITION(conditional, VER_MAJORVERSION, VER_GREATER_EQUAL); |
| is_vista = !!::VerifyVersionInfo(&osvi, VER_MAJORVERSION, conditional); |
| known = true; |
| } |
| return is_vista; |
| } |
| |
| // Adapted from vistautil.cc. |
| HRESULT IsUserRunningSplitToken(bool* is_split_token) { |
| if (!IsVistaOrLater()) { |
| *is_split_token = false; |
| return S_OK; |
| } |
| |
| HANDLE process_token = NULL; |
| if (!::OpenProcessToken(::GetCurrentProcess(), |
| TOKEN_QUERY, |
| &process_token)) { |
| HRESULT hr = HRESULTFromLastError(); |
| ::CloseHandle(process_token); |
| return hr; |
| } |
| |
| TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault; |
| DWORD size_returned = 0; |
| if (!::GetTokenInformation(process_token, |
| TokenElevationType, |
| &elevation_type, |
| sizeof(elevation_type), |
| &size_returned)) { |
| HRESULT hr = HRESULTFromLastError(); |
| ::CloseHandle(process_token); |
| return hr; |
| } |
| |
| ::CloseHandle(process_token); |
| |
| *is_split_token = elevation_type == TokenElevationTypeFull || |
| elevation_type == TokenElevationTypeLimited; |
| |
| return S_OK; |
| } |
| |
| // Adapted from vistautil.cc. |
| bool IsUACDisabled() { |
| if (!IsVistaOrLater()) { |
| return false; |
| } |
| |
| // Split token indicates that UAC is on. |
| bool is_split_token = true; |
| if SUCCEEDED(IsUserRunningSplitToken(&is_split_token)) { |
| return !is_split_token; |
| } else { |
| // Return a safe value on failure. |
| return false; |
| } |
| } |
| |
| // Checking the full path vs. just being somewhere in Program Files is important |
| // because other programs may have lowered the ACLs of some subdirectories. |
| bool IsRunningFromProgramFilesDirectory() { |
| // Get the HMODULE for the current process. |
| HMODULE module_handle = ::GetModuleHandle(NULL); |
| if (!module_handle) { |
| return false; |
| } |
| |
| // Get the full path to the module based on the HMODULE. |
| CString module_path; |
| DWORD result = ::GetModuleFileName(module_handle, |
| module_path.GetBufferSetLength(MAX_PATH), |
| MAX_PATH); |
| module_handle = NULL; |
| |
| if (result == 0) { |
| return false; |
| } |
| |
| // Get the directory of the current process without the filename. |
| CPath path_temp(module_path); |
| path_temp.RemoveFileSpec(); |
| module_path = static_cast<CString>(path_temp); |
| |
| // Get the directory to %ProgramFiles%. |
| TCHAR folder_path_buffer[MAX_PATH] = {0}; |
| HRESULT hr = ::SHGetFolderPath(NULL, |
| CSIDL_PROGRAM_FILES, |
| NULL, |
| SHGFP_TYPE_CURRENT, |
| folder_path_buffer); |
| if (FAILED(hr)) { |
| return false; |
| } |
| |
| // Append the google/update install path onto %ProgramFiles%. |
| CString folder_path = folder_path_buffer; |
| if (!::PathAppend(CStrBuf(folder_path, MAX_PATH), |
| OMAHA_REL_GOOPDATE_INSTALL_DIR)) { |
| return false; |
| } |
| |
| folder_path.MakeLower(); |
| module_path.MakeLower(); |
| |
| // Check if module_path starts with folder_path. |
| return (module_path.Find(folder_path) == 0); |
| } |
| |
| // In the following case, we need to validate the signature of goopdate.dll |
| // before loading it to maintain the chain of trust: |
| // * Not running from a secure location |
| // * Vista and later |
| // * Running elevated/with admin privileges |
| // * UAC is not disabled |
| // |
| // We explicitly do not perform the authenticode check when UAC is disabled |
| // because the other conditions are all satisfied by the per-user instance of |
| // Omaha installed for a member of the admin group. Without an explicit check, |
| // an authenticode check would be performed every time the the per-user instance |
| // runs in these configurations. |
| // Skipping the authenticode check when UAC is disabled is also okay because the |
| // user has opted to let all applications run with the same privilege, so there |
| // is no need elevation of privileges. |
| // TODO(omaha): Eliminate the supported cases where this shell runs elevated |
| // from an unsecure location and replace the authenticode check with an error. |
| HRESULT VerifySignatureIfNecessary(const TCHAR* file_path, |
| bool is_running_from_secure_location) { |
| if (is_running_from_secure_location || |
| !IsVistaOrLater() || |
| !::IsUserAnAdmin()) { |
| return S_OK; |
| } |
| |
| if (IsUACDisabled()) { |
| return S_OK; |
| } |
| |
| // Verify the Authenticode signature but use only the local cache for |
| // revocation checks. |
| HRESULT hr = VerifySignature(file_path, false); |
| #if TEST_CERTIFICATE |
| // The chain of trust will not validate on builds signed with the test |
| // certificate. |
| if (CERT_E_UNTRUSTEDROOT == hr) { |
| hr = S_OK; |
| } |
| #endif |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| // Verify that there is a Google certificate and that it has not expired. |
| if (!VerifySigneeIsGoogle(file_path)) { |
| return GOOGLEUPDATE_E_VERIFY_SIGNEE_IS_GOOGLE_FAILED; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT GetRegisteredVersion(bool is_machine, CString* version) { |
| HKEY key = NULL; |
| LONG res = ::RegOpenKeyEx(is_machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, |
| GOOPDATE_REG_RELATIVE_CLIENTS GOOPDATE_APP_ID, |
| 0, |
| KEY_READ, |
| &key); |
| if (ERROR_SUCCESS != res) { |
| return HRESULT_FROM_WIN32(res); |
| } |
| |
| DWORD type = 0; |
| DWORD version_length = 50; |
| res = ::SHQueryValueEx(key, |
| omaha::kRegValueProductVersion, |
| NULL, |
| &type, |
| CStrBuf(*version, version_length), |
| &version_length); |
| if (ERROR_SUCCESS != res) { |
| return HRESULT_FROM_WIN32(res); |
| } |
| |
| if (REG_SZ != type) { |
| return E_UNEXPECTED; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT GetDllPath(HINSTANCE instance, bool is_machine, CString* dll_path) { |
| TCHAR base_path[MAX_PATH] = {0}; |
| TCHAR path[MAX_PATH] = {0}; |
| |
| if (!::GetModuleFileName(instance, base_path, arraysize(base_path))) { |
| return HRESULTFromLastError(); |
| } |
| ::PathRemoveFileSpec(base_path); |
| |
| // Try the side-by-side DLL first. |
| _tcscpy_s(path, arraysize(path), base_path); |
| if (!::PathAppend(path, omaha::kGoopdateDllName)) { |
| return HRESULTFromLastError(); |
| } |
| if (FileExists(path)) { |
| *dll_path = path; |
| return S_OK; |
| } |
| |
| // Try the version subdirectory. |
| _tcscpy_s(path, arraysize(path), base_path); |
| CString version; |
| HRESULT hr = GetRegisteredVersion(is_machine, &version); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| if (!::PathAppend(path, version)) { |
| return HRESULTFromLastError(); |
| } |
| if (!::PathAppend(path, omaha::kGoopdateDllName)) { |
| return HRESULTFromLastError(); |
| } |
| if (!FileExists(path)) { |
| return GOOGLEUPDATE_E_DLL_NOT_FOUND; |
| } |
| |
| *dll_path = path; |
| return S_OK; |
| } |
| |
| } // namespace omaha |
| |
| // Algorithm: |
| // * Looks for goopdate.dll in the current directory. |
| // * If it is not found, looks for goopdate.dll in a version subdirectory based |
| // on the version found in the registry. |
| // * Verifies the signature of the goopdate.dll file. |
| // * Loads the DLL and calls the entry point. |
| int WINAPI _tWinMain(HINSTANCE instance, |
| HINSTANCE, |
| LPTSTR, |
| int cmd_show) { |
| bool is_running_from_program_files = |
| omaha::IsRunningFromProgramFilesDirectory(); |
| |
| // We assume here that running from program files means we should check |
| // the machine install version and otherwise we should check the user |
| // version. This should be true in all end user cases except for initial |
| // installs from the temp directory, in which case the DLL should be in the |
| // same directory so this value does not get used. |
| // For developer use cases, the DLL should also be in the same directory. |
| bool is_machine = is_running_from_program_files; |
| |
| CString dll_path; |
| HRESULT hr = omaha::GetDllPath(instance, is_machine, &dll_path); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| hr = omaha::VerifySignatureIfNecessary(dll_path, |
| is_running_from_program_files); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| HMODULE module(::LoadLibraryEx(dll_path, NULL, 0)); |
| if (!module) { |
| return omaha::HRESULTFromLastError(); |
| } |
| |
| DllEntry dll_entry = reinterpret_cast<DllEntry>( |
| ::GetProcAddress(module, omaha::kGoopdateDllEntryAnsi)); |
| if (dll_entry) { |
| // We must send in GetCommandLine() and not cmd_line because the command |
| // line parsing code expects to have the program name as the first argument |
| // and cmd_line does not provide this. |
| hr = dll_entry(::GetCommandLine(), cmd_show); |
| } else { |
| hr = E_FAIL; |
| } |
| |
| ::FreeLibrary(module); |
| |
| return hr; |
| } |