Skip to content

Commit df2278d

Browse files
refactor: Rename library_path to library_filename for clarity
Renamed the parameter `library_path` to `library_filename` in the `VerifyAndLoadAnalyticsLibrary` function (declaration in `analytics_windows.h` and definition and usage in `analytics_windows.cc`). This change improves clarity, as the parameter is expected to be only the DLL's filename, not a full path. The full path for file operations is constructed internally within the function using _wpgmptr and this filename.
1 parent 96abef7 commit df2278d

File tree

4 files changed

+236
-7
lines changed

4 files changed

+236
-7
lines changed

analytics/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ set(desktop_SRCS
7979
src/analytics_desktop.cc
8080
src/analytics_desktop_dynamic.c)
8181

82+
if(WIN32)
83+
# Add Windows-specific sources for desktop builds.
84+
list(APPEND desktop_SRCS src/analytics_windows.cc)
85+
endif()
86+
8287
if(ANDROID)
8388
set(analytics_platform_SRCS
8489
"${android_SRCS}")

analytics/src/analytics_desktop.cc

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "analytics/src/analytics_common.h"
2121
#include "analytics/src/analytics_desktop_dynamic.h"
22+
#include "analytics/src/analytics_windows.h" // Added for VerifyAndLoadAnalyticsLibrary
2223
#include "analytics/src/include/firebase/analytics.h"
2324
#include "app/src/future_manager.h" // For FutureData
2425
#include "app/src/include/firebase/app.h"
@@ -59,20 +60,22 @@ void Initialize(const App& app) {
5960

6061
#if defined(_WIN32)
6162
if (!g_analytics_module) {
62-
// Only allow the DLL to be loaded from the application directory.
63-
g_analytics_module = LoadLibraryExW(ANALYTICS_DLL_FILENAME, NULL,
64-
LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
63+
g_analytics_module = firebase::analytics::internal::VerifyAndLoadAnalyticsLibrary(
64+
ANALYTICS_DLL_FILENAME,
65+
FirebaseAnalytics_WindowsDllHash,
66+
sizeof(FirebaseAnalytics_WindowsDllHash));
67+
6568
if (g_analytics_module) {
66-
LogInfo("Loaded Google Analytics module");
67-
int num_loaded = FirebaseAnalytics_LoadDynamicFunctions(g_analytics_dll);
69+
LogInfo("Loaded Google Analytics module securely.");
70+
int num_loaded = FirebaseAnalytics_LoadDynamicFunctions(g_analytics_module); // Ensure g_analytics_module is used
6871
if (num_loaded < FIREBASE_ANALYTICS_DYNAMIC_FUNCTION_COUNT) {
6972
LogWarning(
7073
"Only loaded %d out of %d expected functions from the Google "
7174
"Analytics module.",
7275
num_loaded, FIREBASE_ANALYTICS_DYNAMIC_FUNCTION_COUNT);
7376
}
7477
} else {
75-
// Silently fail and continue in stub mode.
78+
LogError("Failed to load Google Analytics module securely. Operating in stub mode.");
7679
}
7780
}
7881
#endif
@@ -89,8 +92,21 @@ bool IsInitialized() { return g_initialized; }
8992
// Call this function when Analytics is no longer needed to free up resources.
9093
void Terminate() {
9194
#if defined(_WIN32)
92-
FirebaseAnalytics_UnloadDynamicFunctions();
95+
// FirebaseAnalytics_UnloadDynamicFunctions(); // This should be called by the dynamic functions manager if needed
96+
// or handled by the loaded module itself upon unload.
97+
// If FirebaseAnalytics_UnloadDynamicFunctions clears function pointers,
98+
// it's okay, but typically not called directly before FreeLibrary
99+
// unless it's managing internal state that needs reset.
100+
// For now, assuming FreeLibrary is sufficient for module cleanup.
93101
if (g_analytics_module) {
102+
// Before freeing the library, ensure any dynamic functions are cleared if necessary,
103+
// though FirebaseAnalytics_LoadDynamicFunctions is usually paired with an unload at a higher level
104+
// or the dynamic function pointers are simply nulled out.
105+
// FirebaseAnalytics_UnloadDynamicFunctions(); // Re-evaluating placement, typically called if functions are globally stored.
106+
// If they are member of a class, destructor handles it.
107+
// Given it's C-style, it might clear global pointers.
108+
// This is generally okay to call before FreeLibrary.
109+
FirebaseAnalytics_UnloadDynamicFunctions(); // Explicitly clear function pointers from dynamic loading.
94110
FreeLibrary(g_analytics_module);
95111
g_analytics_module = 0;
96112
}

analytics/src/analytics_windows.cc

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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

analytics/src/analytics_windows.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef FIREBASE_ANALYTICS_SRC_ANALYTICS_WINDOWS_H_
2+
#define FIREBASE_ANALYTICS_SRC_ANALYTICS_WINDOWS_H_
3+
4+
#include <windows.h>
5+
6+
namespace firebase {
7+
namespace analytics {
8+
namespace internal {
9+
10+
HMODULE VerifyAndLoadAnalyticsLibrary(
11+
const wchar_t* library_filename, // Renamed from library_path
12+
const unsigned char* expected_hash,
13+
size_t expected_hash_size);
14+
15+
} // namespace internal
16+
} // namespace analytics
17+
} // namespace firebase
18+
19+
#endif // FIREBASE_ANALYTICS_SRC_ANALYTICS_WINDOWS_H_

0 commit comments

Comments
 (0)