Index: recovery/client/google_update_recovery.cc |
diff --git a/recovery/client/google_update_recovery.cc b/recovery/client/google_update_recovery.cc |
deleted file mode 100644 |
index 93a7fa8052bf4491e02aea36e4e888560d32a9de..0000000000000000000000000000000000000000 |
--- a/recovery/client/google_update_recovery.cc |
+++ /dev/null |
@@ -1,626 +0,0 @@ |
-// Copyright 2007-2010 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. |
-// ======================================================================== |
- |
-// |
-// Implementation of the Google Update recovery mechanism to be included in |
-// Google apps. |
- |
-#include "omaha/recovery/client/google_update_recovery.h" |
-#include <shellapi.h> |
-#include <wininet.h> |
-#include <atlstr.h> |
-#include "omaha/base/const_addresses.h" |
-#include "omaha/base/signaturevalidator.h" |
-#include "omaha/common/const_group_policy.h" |
-#include "omaha/third_party/smartany/scoped_any.h" |
- |
-namespace omaha { |
- |
-namespace { |
- |
-const int kRollbackWindowDays = 100; |
- |
-const TCHAR* const kMachineRepairArgs = _T("/recover /machine"); |
-const TCHAR* const kUserRepairArgs = _T("/recover"); |
- |
-// TODO(omaha): Add a Code Red lib version that is manually updated when |
-// we check in the lib. |
-const TCHAR* const kQueryStringFormat = |
- _T("?appid=%s&appversion=%s&applang=%s&machine=%u") |
- _T("&version=%s&osversion=%s&servicepack=%s"); |
- |
-// Information about where to obtain Omaha info. |
-// This must never change in Omaha. |
-const TCHAR* const kRegValueProductVersion = _T("pv"); |
-const TCHAR* const kRelativeGoopdateRegPath = _T("Software\\Google\\Update\\"); |
-const TCHAR* const kRelativeClientsGoopdateRegPath = |
- _T("Software\\Google\\Update\\Clients\\") |
- _T("{430FD4D0-B729-4F61-AA34-91526481799D}"); |
- |
-// The UpdateDev registry value to override the Code Red url. |
-const TCHAR* const kRegValueNameCodeRedUrl = _T("CodeRedUrl"); |
- |
-// Starts another process via ::CreateProcess. |
-HRESULT StartProcess(const TCHAR* process_name, TCHAR* command_line) { |
- if (!process_name && !command_line) { |
- return E_INVALIDARG; |
- } |
- |
- PROCESS_INFORMATION pi = {0}; |
- STARTUPINFO si = {sizeof(si), 0}; |
- |
- // Feedback cursor is off while the process is starting. |
- si.dwFlags = STARTF_FORCEOFFFEEDBACK; |
- |
- BOOL success = ::CreateProcess( |
- process_name, // Module name |
- command_line, // Command line |
- NULL, // Process handle not inheritable |
- NULL, // Thread handle not inheritable |
- FALSE, // Set handle inheritance to FALSE |
- 0, // No creation flags |
- NULL, // Use parent's environment block |
- NULL, // Use parent's starting directory |
- &si, // Pointer to STARTUPINFO structure |
- &pi); // Pointer to PROCESS_INFORMATION structure |
- |
- if (!success) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } |
- |
- ::CloseHandle(pi.hProcess); |
- ::CloseHandle(pi.hThread); |
- |
- return S_OK; |
-} |
- |
-// Check if a string starts with another string. Case-sensitive. |
-bool StringStartsWith(const TCHAR *str, const TCHAR *start_str) { |
- if (!start_str || !str) { |
- return false; |
- } |
- |
- while (0 != *str) { |
- // Check for matching characters |
- TCHAR c1 = *str; |
- TCHAR c2 = *start_str; |
- |
- // Reached the end of start_str? |
- if (0 == c2) |
- return true; |
- |
- if (c1 != c2) |
- return false; |
- |
- ++str; |
- ++start_str; |
- } |
- |
- // If str is shorter than start_str, no match. If equal size, match. |
- return 0 == *start_str; |
-} |
- |
-// Escape and unescape strings (shlwapi-based implementation). |
-// The intended usage for these APIs is escaping strings to make up |
-// URLs, for example building query strings. |
-// |
-// Pass false to the flag segment_only to escape the url. This will not |
-// cause the conversion of the # (%23), ? (%3F), and / (%2F) characters. |
- |
-// Characters that must be encoded include any characters that have no |
-// corresponding graphic character in the US-ASCII coded character |
-// set (hexadecimal 80-FF, which are not used in the US-ASCII coded character |
-// set, and hexadecimal 00-1F and 7F, which are control characters), |
-// blank spaces, "%" (which is used to encode other characters), |
-// and unsafe characters (<, >, ", #, {, }, |, \, ^, ~, [, ], and '). |
-// |
-// The input and output strings can't be longer than INTERNET_MAX_URL_LENGTH |
- |
-HRESULT StringEscape(const CString& str_in, |
- bool segment_only, |
- CString* escaped_string) { |
- if (!escaped_string) { |
- return E_INVALIDARG; |
- } |
- |
- DWORD buf_len = INTERNET_MAX_URL_LENGTH + 1; |
- HRESULT hr = ::UrlEscape(str_in, |
- escaped_string->GetBufferSetLength(buf_len), |
- &buf_len, |
- segment_only ? |
- URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY : |
- URL_ESCAPE_PERCENT); |
- if (SUCCEEDED(hr)) { |
- escaped_string->ReleaseBuffer(); |
- } |
- return hr; |
-} |
- |
-// Gets the temporary files directory for the current user. |
-// The directory returned may not exist. |
-// The returned path ends with a '\'. |
-// Fails if the path is longer than MAX_PATH. |
-HRESULT GetTempDir(CString* temp_path) { |
- if (!temp_path) { |
- return E_INVALIDARG; |
- } |
- |
- temp_path->Empty(); |
- |
- TCHAR buffer[MAX_PATH] = {0}; |
- DWORD num_chars = ::GetTempPath(MAX_PATH, buffer); |
- if (!num_chars) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } else if (num_chars >= MAX_PATH) { |
- return E_FAIL; |
- } |
- |
- *temp_path = buffer; |
- return S_OK; |
-} |
- |
-// Creates the specified directory. |
-HRESULT CreateDir(const CString& dir) { |
- if (!::CreateDirectory(dir, NULL)) { |
- DWORD error = ::GetLastError(); |
- if (ERROR_FILE_EXISTS != error && ERROR_ALREADY_EXISTS != error) { |
- return HRESULT_FROM_WIN32(error); |
- } |
- } |
- return S_OK; |
-} |
- |
-HRESULT GetAndCreateTempDir(CString* temp_path) { |
- if (!temp_path) { |
- return E_INVALIDARG; |
- } |
- |
- HRESULT hr = GetTempDir(temp_path); |
- if (FAILED(hr)) { |
- return hr; |
- } |
- if (temp_path->IsEmpty()) { |
- return E_FAIL; |
- } |
- |
- // Create this dir if it doesn't already exist. |
- return CreateDir(*temp_path); |
-} |
- |
- |
-// Create a unique temporary file and returns the full path. |
-HRESULT CreateUniqueTempFile(const CString& user_temp_dir, |
- CString* unique_temp_file_path) { |
- if (user_temp_dir.IsEmpty() || !unique_temp_file_path) { |
- return E_INVALIDARG; |
- } |
- |
- TCHAR unique_temp_filename[MAX_PATH] = {0}; |
- if (!::GetTempFileName(user_temp_dir, |
- _T("GUR"), // prefix |
- 0, // form a unique filename |
- unique_temp_filename)) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } |
- |
- *unique_temp_file_path = unique_temp_filename; |
- if (unique_temp_file_path->IsEmpty()) { |
- return E_FAIL; |
- } |
- |
- return S_OK; |
-} |
- |
-// Obtains the OS version and service pack. |
-HRESULT GetOSInfo(CString* os_version, CString* service_pack) { |
- if (!os_version || !service_pack) { |
- return E_INVALIDARG; |
- } |
- |
- OSVERSIONINFO os_version_info = { 0 }; |
- os_version_info.dwOSVersionInfoSize = sizeof(os_version_info); |
- if (!::GetVersionEx(&os_version_info)) { |
- HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
- return hr; |
- } else { |
- os_version->Format(_T("%d.%d"), |
- os_version_info.dwMajorVersion, |
- os_version_info.dwMinorVersion); |
- *service_pack = os_version_info.szCSDVersion; |
- } |
- return S_OK; |
-} |
- |
-// Reads the specified string value from the specified registry key. |
-// Only supports value types REG_SZ and REG_EXPAND_SZ. |
-// REG_EXPAND_SZ strings are not expanded. |
-HRESULT GetRegStringValue(bool is_machine_key, |
- const CString& relative_key_path, |
- const CString& value_name, |
- CString* value) { |
- if (!value) { |
- return E_INVALIDARG; |
- } |
- |
- value->Empty(); |
- HKEY root_key = is_machine_key ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
- HKEY key = NULL; |
- LONG res = ::RegOpenKeyEx(root_key, relative_key_path, 0, KEY_READ, &key); |
- if (res != ERROR_SUCCESS) { |
- return HRESULT_FROM_WIN32(res); |
- } |
- |
- // First get the size of the string buffer. |
- DWORD type = 0; |
- DWORD byte_count = 0; |
- res = ::RegQueryValueEx(key, value_name, NULL, &type, NULL, &byte_count); |
- if (ERROR_SUCCESS != res) { |
- ::RegCloseKey(key); |
- return HRESULT_FROM_WIN32(res); |
- } |
- if ((type != REG_SZ && type != REG_EXPAND_SZ) || (0 == byte_count)) { |
- ::RegCloseKey(key); |
- return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); |
- } |
- |
- CString local_value; |
- // GetBuffer throws when not able to allocate the requested buffer. |
- TCHAR* buffer = local_value.GetBuffer(byte_count / sizeof(TCHAR)); |
- res = ::RegQueryValueEx(key, |
- value_name, |
- NULL, |
- NULL, |
- reinterpret_cast<BYTE*>(buffer), |
- &byte_count); |
- ::RegCloseKey(key); |
- if (ERROR_SUCCESS == res) { |
- local_value.ReleaseBufferSetLength(byte_count / sizeof(TCHAR)); |
- *value = local_value; |
- } |
- |
- return HRESULT_FROM_WIN32(res); |
-} |
- |
-// Reads the specified DWORD value from the specified registry key. |
-// Only supports value types REG_DWORD. |
-// Assumes DWORD is sufficient buffer, which must be true for valid value type. |
-HRESULT GetRegDwordValue(bool is_machine_key, |
- const CString& relative_key_path, |
- const CString& value_name, |
- DWORD* value) { |
- if (!value) { |
- return E_INVALIDARG; |
- } |
- |
- HKEY root_key = is_machine_key ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
- HKEY key = NULL; |
- LONG res = ::RegOpenKeyEx(root_key, relative_key_path, 0, KEY_READ, &key); |
- if (res != ERROR_SUCCESS) { |
- return HRESULT_FROM_WIN32(res); |
- } |
- |
- DWORD type = 0; |
- DWORD byte_count = sizeof(*value); |
- res = ::RegQueryValueEx(key, |
- value_name, |
- NULL, |
- &type, |
- reinterpret_cast<BYTE*>(value), |
- &byte_count); |
- ::RegCloseKey(key); |
- if (ERROR_SUCCESS != res) { |
- return HRESULT_FROM_WIN32(res); |
- } |
- if ((type != REG_DWORD) || (0 == byte_count)) { |
- return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); |
- } |
- |
- return S_OK; |
-} |
- |
-// Obtains information about the current Omaha installation. |
-// Attempts to obtain as much information as possible even if errors occur. |
-// Therefore, return values of GetRegStringValue are ignored. |
-HRESULT GetOmahaInformation(bool is_machine_app, |
- CString* omaha_version) { |
- if (!omaha_version) { |
- return E_INVALIDARG; |
- } |
- |
- if (FAILED(GetRegStringValue(is_machine_app, |
- kRelativeClientsGoopdateRegPath, |
- kRegValueProductVersion, |
- omaha_version))) { |
- *omaha_version = _T("0.0.0.0"); |
- } |
- |
- return S_OK; |
-} |
- |
-// Builds the query portion of the recovery url. |
-// This method obtains values necessary to build the query that are not provided |
-// as parameters. |
-// Attempts to build with as much information as possible even if errors occur. |
-HRESULT BuildUrlQueryPortion(const CString& app_guid, |
- const CString& app_version, |
- const CString& app_language, |
- bool is_machine_app, |
- CString* query) { |
- if (!query) { |
- return E_INVALIDARG; |
- } |
- |
- CString omaha_version; |
- GetOmahaInformation(is_machine_app, &omaha_version); |
- |
- CString os_version; |
- CString os_service_pack; |
- GetOSInfo(&os_version, &os_service_pack); |
- |
- // All parameters must be escaped individually before building the query. |
- CString app_guid_escaped; |
- CString app_version_escaped; |
- CString app_language_escaped; |
- CString omaha_version_escaped; |
- CString os_version_escaped; |
- CString os_service_pack_escaped; |
- StringEscape(app_guid, true, &app_guid_escaped); |
- StringEscape(app_version, true, &app_version_escaped); |
- StringEscape(app_language, true, &app_language_escaped); |
- StringEscape(omaha_version, true, &omaha_version_escaped); |
- StringEscape(os_version, true, &os_version_escaped); |
- StringEscape(os_service_pack, true, &os_service_pack_escaped); |
- |
- query->Format(kQueryStringFormat, |
- app_guid_escaped, |
- app_version_escaped, |
- app_language_escaped, |
- is_machine_app ? 1 : 0, |
- omaha_version_escaped, |
- os_version_escaped, |
- os_service_pack_escaped); |
- |
- return S_OK; |
-} |
- |
-// Returns the full path to save the downloaded file to. |
-// The path is based on a unique temporary filename to avoid a conflict |
-// between multiple apps downloading to the same location. |
-// The path to this file is also returned. The caller is responsible for |
-// deleting the temporary file after using the download target path. |
-// If it cannot create the unique directory, it attempts to use the user's |
-// temporary directory and a constant filename. |
-HRESULT GetDownloadTargetPath(CString* download_target_path, |
- CString* temp_file_path) { |
- if (!download_target_path || !temp_file_path) { |
- return E_INVALIDARG; |
- } |
- |
- CString user_temp_dir; |
- HRESULT hr = GetAndCreateTempDir(&user_temp_dir); |
- if (FAILED(hr)) { |
- return hr; |
- } |
- |
- hr = CreateUniqueTempFile(user_temp_dir, temp_file_path); |
- if (SUCCEEDED(hr) && !temp_file_path->IsEmpty()) { |
- *download_target_path = *temp_file_path; |
- // Ignore the return value. A .tmp filename is better than none. |
- download_target_path->Replace(_T(".tmp"), _T(".exe")); |
- } else { |
- // Try a static filename in the temp directory as a fallback. |
- *download_target_path = user_temp_dir + _T("GoogleUpdateSetup.exe"); |
- *temp_file_path = _T(""); |
- } |
- |
- return S_OK; |
-} |
- |
-HRESULT DownloadRepairFile(const CString& download_target_path, |
- const CString& app_guid, |
- const CString& app_version, |
- const CString& app_language, |
- bool is_machine_app, |
- DownloadCallback download_callback, |
- void* context) { |
- CString query; |
- BuildUrlQueryPortion(app_guid, |
- app_version, |
- app_language, |
- is_machine_app, |
- &query); |
- |
- CString url; |
- HRESULT hr = GetRegStringValue(true, |
- _T("SOFTWARE\\Google\\UpdateDev"), |
- kRegValueNameCodeRedUrl, |
- &url); |
- if (FAILED(hr)) { |
- url = omaha::kUrlCodeRedCheck; |
- } |
- |
- url += query; |
- |
- return download_callback(url, download_target_path, context); |
-} |
- |
-// Makes sure the path is enclosed with double quotation marks. |
-void EnclosePath(CString* path) { |
- if (path) { |
- return; |
- } |
- |
- if (!path->IsEmpty() && path->GetAt(0) != _T('"')) { |
- path->Insert(0, _T('"')); |
- path->AppendChar(_T('"')); |
- } |
-} |
- |
-HRESULT RunRepairFile(const CString& file_path, bool is_machine_app) { |
- const TCHAR* repair_file_args = is_machine_app ? kMachineRepairArgs : |
- kUserRepairArgs; |
- |
- CString command_line(file_path); |
- EnclosePath(&command_line); |
- command_line.AppendChar(_T(' ')); |
- command_line.Append(repair_file_args); |
- |
- return StartProcess(NULL, command_line.GetBuffer()); |
-} |
- |
-} // namespace |
- |
-// Verifies the file's integrity and that it is signed by Google. |
-// We cannot prevent rollback attacks by using a version because the client |
-// may not be able to determine the current version if the files and/or |
-// registry entries have been deleted/corrupted. |
-// Therefore, we check that the file was signed recently. |
-HRESULT VerifyFileSignature(const CString& filename) { |
- // Use Authenticode/WinVerifyTrust to verify the file. |
- // Allow the revocation check to use the network. |
- HRESULT hr = VerifySignature(filename, true); |
- if (FAILED(hr)) { |
- return hr; |
- } |
- |
- // Verify that there is a Google certificate. |
- if (!VerifySigneeIsGoogle(filename)) { |
- return CERT_E_CN_NO_MATCH; |
- } |
- |
- // Check that the file was signed recently to limit the window for |
- // rollback attacks. |
- return VerifyFileSignedWithinDays(filename, kRollbackWindowDays); |
-} |
- |
-// Verifies the file contains the special markup resource for repair files. |
-HRESULT VerifyRepairFileMarkup(const CString& filename) { |
- const TCHAR* kMarkupResourceName = MAKEINTRESOURCE(1); |
- const TCHAR* kMarkupResourceType = _T("GOOGLEUPDATEREPAIR"); |
- const DWORD kMarkupResourceExpectedValue = 1; |
- |
- scoped_library module(::LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE)); |
- if (!module) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } |
- |
- HRSRC resource(::FindResource(get(module), |
- kMarkupResourceName, |
- kMarkupResourceType)); |
- if (!resource) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } |
- |
- if (sizeof(kMarkupResourceExpectedValue) != |
- ::SizeofResource(get(module), resource)) { |
- return E_UNEXPECTED; |
- } |
- |
- HGLOBAL loaded_resource(::LoadResource(get(module), resource)); |
- if (!loaded_resource) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } |
- |
- const DWORD* value = static_cast<DWORD*>(::LockResource(loaded_resource)); |
- if (!value) { |
- return E_HANDLE; |
- } |
- |
- if (kMarkupResourceExpectedValue != *value) { |
- return E_UNEXPECTED; |
- } |
- |
- return S_OK; |
-} |
- |
-// Verifies the filename is not UNC name, the file exists, has a valid signature |
-// chain, is signed by Google, and contains the special markup resource for |
-// repair files. |
-HRESULT VerifyIsValidRepairFile(const CString& filename) { |
- // Make sure file exists. |
- if (!::PathFileExists(filename)) { |
- return HRESULT_FROM_WIN32(::GetLastError()); |
- } |
- |
- HRESULT hr = VerifyFileSignature(filename); |
- if (FAILED(hr)) { |
- return hr; |
- } |
- |
- return VerifyRepairFileMarkup(filename); |
-} |
- |
-} // namespace omaha |
- |
-// If a repair file is run, the file will not be deleted until reboot. Delete |
-// after reboot will only succeed when executed by an admin or LocalSystem. |
-// Returns HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY) if automatic |
-// update checks are disabled. |
-HRESULT FixGoogleUpdate(const TCHAR* app_guid, |
- const TCHAR* app_version, |
- const TCHAR* app_language, |
- bool is_machine_app, |
- DownloadCallback download_callback, |
- void* context) { |
- if (!app_guid || !app_version || !app_language || !download_callback) { |
- return E_INVALIDARG; |
- } |
- |
- DWORD update_check_period_override_minutes(UINT_MAX); |
- HRESULT hr = omaha::GetRegDwordValue( |
- true, |
- GOOPDATE_POLICIES_RELATIVE, |
- omaha::kRegValueAutoUpdateCheckPeriodOverrideMinutes, |
- &update_check_period_override_minutes); |
- if (SUCCEEDED(hr) && (0 == update_check_period_override_minutes)) { |
- return HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY); |
- } |
- |
- CString download_target_path; |
- CString temp_file_path; |
- hr = omaha::GetDownloadTargetPath(&download_target_path, &temp_file_path); |
- if (FAILED(hr)) { |
- return hr; |
- } |
- if (download_target_path.IsEmpty()) { |
- return E_FAIL; |
- } |
- |
- // After calling DownloadRepairFile, don't return until the repair file and |
- // temp file have been deleted. |
- hr = omaha::DownloadRepairFile(download_target_path, |
- app_guid, |
- app_version, |
- app_language, |
- is_machine_app, |
- download_callback, |
- context); |
- |
- if (SUCCEEDED(hr)) { |
- hr = omaha::VerifyIsValidRepairFile(download_target_path); |
- } |
- |
- if (FAILED(hr)) { |
- ::DeleteFile(download_target_path); |
- ::DeleteFile(temp_file_path); |
- return hr; |
- } |
- |
- hr = omaha::RunRepairFile(download_target_path, is_machine_app); |
- ::MoveFileEx(download_target_path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); |
- ::DeleteFile(temp_file_path); |
- |
- return hr; |
-} |