|
| 1 | +#include "analytics_windows.h" |
| 2 | + |
| 3 | +#include <windows.h> |
| 4 | +#include <wincrypt.h> |
| 5 | +#include <vector> |
| 6 | +#include <string> // Required for std::wstring |
| 7 | +#include <cstdlib> // Required for _wpgmptr |
| 8 | +#include <cstring> // For memcmp |
| 9 | +#include "app/src/include/firebase/log.h" //NOLINT |
| 10 | + |
| 11 | +namespace firebase { |
| 12 | +namespace analytics { |
| 13 | +namespace internal { |
| 14 | + |
| 15 | +// Helper function to calculate SHA256 hash of a file. |
| 16 | +static std::vector<BYTE> CalculateFileSha256(HANDLE hFile) { |
| 17 | + HCRYPTPROV hProv = 0; |
| 18 | + HCRYPTHASH hHash = 0; |
| 19 | + std::vector<BYTE> result_hash_value; |
| 20 | + |
| 21 | + if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { |
| 22 | + DWORD dwError = GetLastError(); |
| 23 | + LogError("CalculateFileSha256: SetFilePointer failed. Error: %u", dwError); |
| 24 | + return result_hash_value; |
| 25 | + } |
| 26 | + |
| 27 | + // Acquire Crypto Provider. |
| 28 | + // Using CRYPT_VERIFYCONTEXT for operations that don't require private key access. |
| 29 | + if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { |
| 30 | + DWORD dwError = GetLastError(); |
| 31 | + LogError("CalculateFileSha256: CryptAcquireContextW failed. Error: %u", dwError); |
| 32 | + return result_hash_value; |
| 33 | + } |
| 34 | + |
| 35 | + if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) { |
| 36 | + DWORD dwError = GetLastError(); |
| 37 | + LogError("CalculateFileSha256: CryptCreateHash failed. Error: %u", dwError); |
| 38 | + CryptReleaseContext(hProv, 0); |
| 39 | + return result_hash_value; |
| 40 | + } |
| 41 | + |
| 42 | + BYTE rgbFile[1024]; |
| 43 | + DWORD cbRead = 0; |
| 44 | + BOOL bReadSuccessLoop = TRUE; |
| 45 | + |
| 46 | + while (true) { |
| 47 | + bReadSuccessLoop = ReadFile(hFile, rgbFile, sizeof(rgbFile), &cbRead, NULL); |
| 48 | + if (!bReadSuccessLoop) { |
| 49 | + DWORD dwError = GetLastError(); |
| 50 | + LogError("CalculateFileSha256: ReadFile failed. Error: %u", dwError); |
| 51 | + CryptDestroyHash(hHash); |
| 52 | + CryptReleaseContext(hProv, 0); |
| 53 | + return result_hash_value; |
| 54 | + } |
| 55 | + if (cbRead == 0) { |
| 56 | + break; |
| 57 | + } |
| 58 | + if (!CryptHashData(hHash, rgbFile, cbRead, 0)) { |
| 59 | + DWORD dwError = GetLastError(); |
| 60 | + LogError("CalculateFileSha256: CryptHashData failed. Error: %u", dwError); |
| 61 | + CryptDestroyHash(hHash); |
| 62 | + CryptReleaseContext(hProv, 0); |
| 63 | + return result_hash_value; |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + DWORD cbHashValue = 0; |
| 68 | + DWORD dwCount = sizeof(DWORD); |
| 69 | + if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&cbHashValue, &dwCount, 0)) { |
| 70 | + DWORD dwError = GetLastError(); |
| 71 | + LogError("CalculateFileSha256: CryptGetHashParam (HP_HASHSIZE) failed. Error: %u", dwError); |
| 72 | + CryptDestroyHash(hHash); |
| 73 | + CryptReleaseContext(hProv, 0); |
| 74 | + return result_hash_value; |
| 75 | + } |
| 76 | + |
| 77 | + result_hash_value.resize(cbHashValue); |
| 78 | + if (!CryptGetHashParam(hHash, HP_HASHVAL, result_hash_value.data(), &cbHashValue, 0)) { |
| 79 | + DWORD dwError = GetLastError(); |
| 80 | + LogError("CalculateFileSha256: CryptGetHashParam (HP_HASHVAL) failed. Error: %u", dwError); |
| 81 | + result_hash_value.clear(); |
| 82 | + CryptDestroyHash(hHash); |
| 83 | + CryptReleaseContext(hProv, 0); |
| 84 | + return result_hash_value; |
| 85 | + } |
| 86 | + |
| 87 | + CryptDestroyHash(hHash); |
| 88 | + CryptReleaseContext(hProv, 0); |
| 89 | + return result_hash_value; |
| 90 | +} |
| 91 | + |
| 92 | +HMODULE VerifyAndLoadAnalyticsLibrary( |
| 93 | + const wchar_t* library_filename, // This is expected to be just the DLL filename e.g. "analytics_win.dll" |
| 94 | + const unsigned char* expected_hash, |
| 95 | + size_t expected_hash_size) { |
| 96 | + |
| 97 | + if (library_filename == nullptr || library_filename[0] == L'\0' || |
| 98 | + expected_hash == nullptr || expected_hash_size == 0) { |
| 99 | + LogError("VerifyAndLoadAnalyticsLibrary: Invalid arguments provided. Library path or hash details are missing."); |
| 100 | + return nullptr; |
| 101 | + } |
| 102 | + |
| 103 | + // Get full path to the executable using _wpgmptr. |
| 104 | + // This global variable is provided by the CRT and is expected to be available on Windows. |
| 105 | + if (_wpgmptr == nullptr || _wpgmptr[0] == L'\0') { |
| 106 | + LogError("VerifyAndLoadAnalyticsLibrary: _wpgmptr is null or empty, cannot determine executable path."); |
| 107 | + return nullptr; |
| 108 | + } |
| 109 | + std::wstring executable_path_str(_wpgmptr); |
| 110 | + |
| 111 | + size_t last_slash_pos = executable_path_str.find_last_of(L"\\"); |
| 112 | + if (last_slash_pos == std::wstring::npos) { |
| 113 | + LogError("VerifyAndLoadAnalyticsLibrary: Could not determine executable directory from _wpgmptr (no backslash found)."); |
| 114 | + return nullptr; |
| 115 | + } |
| 116 | + |
| 117 | + std::wstring full_dll_path_str = executable_path_str.substr(0, last_slash_pos + 1); |
| 118 | + full_dll_path_str += library_filename; // library_filename is the filename |
| 119 | + |
| 120 | + HANDLE hFile = CreateFileW(full_dll_path_str.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, |
| 121 | + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| 122 | + if (hFile == INVALID_HANDLE_VALUE) { |
| 123 | + DWORD dwError = GetLastError(); |
| 124 | + // If the DLL is simply not found, silently proceed to stub mode without logging an error. |
| 125 | + // For other errors (e.g., access denied on an existing file), log them as it's an unexpected issue. |
| 126 | + if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { |
| 127 | + LogError("VerifyAndLoadAnalyticsLibrary: Failed to open the Analytics DLL (it may be present but inaccessible). Error: %u", dwError); |
| 128 | + } |
| 129 | + return nullptr; // In all CreateFileW failure cases, return nullptr to fall back to stub mode. |
| 130 | + } |
| 131 | + |
| 132 | + OVERLAPPED overlapped = {0}; |
| 133 | + // Attempt to lock the entire file exclusively (LOCKFILE_EXCLUSIVE_LOCK). |
| 134 | + // This helps ensure no other process modifies the file while we are verifying and loading it. |
| 135 | + BOOL bFileLocked = LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, 0xFFFFFFFF, 0xFFFFFFFF, &overlapped); |
| 136 | + if (!bFileLocked) { |
| 137 | + DWORD dwError = GetLastError(); |
| 138 | + LogError("VerifyAndLoadAnalyticsLibrary: LockFileEx failed for the DLL. Error: %u", dwError); |
| 139 | + CloseHandle(hFile); |
| 140 | + return nullptr; |
| 141 | + } |
| 142 | + |
| 143 | + HMODULE hModule = nullptr; |
| 144 | + |
| 145 | + std::vector<BYTE> calculated_hash = CalculateFileSha256(hFile); |
| 146 | + |
| 147 | + if (calculated_hash.empty()) { |
| 148 | + LogError("VerifyAndLoadAnalyticsLibrary: SHA256 hash calculation failed for the DLL."); |
| 149 | + } else { |
| 150 | + if (calculated_hash.size() != expected_hash_size) { |
| 151 | + LogError("VerifyAndLoadAnalyticsLibrary: Hash size mismatch for DLL. Expected: %zu, Calculated: %zu.", |
| 152 | + expected_hash_size, calculated_hash.size()); |
| 153 | + } else if (memcmp(calculated_hash.data(), expected_hash, expected_hash_size) != 0) { |
| 154 | + LogError("VerifyAndLoadAnalyticsLibrary: SHA256 hash mismatch for the DLL."); |
| 155 | + } else { |
| 156 | + // Load the library. LOAD_LIBRARY_SEARCH_APPLICATION_DIR is a security measure |
| 157 | + // to help ensure that the DLL is loaded from the application's installation directory, |
| 158 | + // mitigating risks of DLL preloading attacks from other locations. |
| 159 | + // Crucially, LoadLibraryExW with this flag needs the DLL *filename only* (library_filename), |
| 160 | + // not the full path we constructed for CreateFileW. |
| 161 | + hModule = LoadLibraryExW(library_filename, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR); |
| 162 | + if (hModule == NULL) { |
| 163 | + DWORD dwError = GetLastError(); |
| 164 | + LogError("VerifyAndLoadAnalyticsLibrary: LoadLibraryExW failed for the DLL after hash verification. Error: %u", dwError); |
| 165 | + } else { |
| 166 | + LogInfo("VerifyAndLoadAnalyticsLibrary: DLL loaded successfully at address %p.", hModule); |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + if (bFileLocked) { |
| 172 | + if (!UnlockFileEx(hFile, 0, 0xFFFFFFFF, 0xFFFFFFFF, &overlapped)) { |
| 173 | + DWORD dwError = GetLastError(); |
| 174 | + LogError("VerifyAndLoadAnalyticsLibrary: UnlockFileEx failed for the DLL. Error: %u", dwError); |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + if (hFile != INVALID_HANDLE_VALUE) { |
| 179 | + if (!CloseHandle(hFile)) { |
| 180 | + DWORD dwError = GetLastError(); |
| 181 | + LogError("VerifyAndLoadAnalyticsLibrary: CloseHandle failed for the DLL. Error: %u", dwError); |
| 182 | + } |
| 183 | + } |
| 184 | + return hModule; |
| 185 | +} |
| 186 | + |
| 187 | +} // namespace internal |
| 188 | +} // namespace analytics |
| 189 | +} // namespace firebase |
0 commit comments