| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome_elf/third_party_dlls/packed_list_file.h" |
| |
| #include <windows.h> |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include <algorithm> |
| #include <limits> |
| |
| #include "chrome/install_static/user_data_dir.h" |
| #include "chrome_elf/sha1/sha1.h" |
| #include "chrome_elf/third_party_dlls/packed_list_format.h" |
| |
| namespace third_party_dlls { |
| namespace { |
| |
| // No concern about concurrency control in chrome_elf. |
| bool g_initialized = false; |
| |
| // This will hold a packed blacklist module array, read directly from a |
| // data file during InitFromFile(). |
| PackedListModule* g_bl_module_array = nullptr; |
| size_t g_bl_module_array_size = 0; |
| |
| // NOTE: this "global" is only initialized once during InitComponent(). |
| // NOTE: it is wrapped in a function to prevent exit-time dtors. |
| std::wstring& GetBlFilePath() { |
| static std::wstring* const file_path = new std::wstring(); |
| return *file_path; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Private functions |
| //------------------------------------------------------------------------------ |
| |
| // Binary predicate compare function for use with |
| // std::equal_range/std::is_sorted. Must return TRUE if lhs < rhs. |
| bool HashBinaryPredicate(const PackedListModule& lhs, |
| const PackedListModule& rhs) { |
| return elf_sha1::CompareHashes(lhs.basename_hash, rhs.basename_hash) < 0; |
| } |
| |
| // Given a file opened for read, pull in the packed list. |
| // |
| // - Returns kSuccess or kArraySizeZero on success. |
| FileStatus ReadInArray(HANDLE file, |
| size_t* array_size, |
| PackedListModule** array_ptr) { |
| PackedListMetadata metadata; |
| DWORD bytes_read = 0; |
| |
| if (!::ReadFile(file, &metadata, sizeof(PackedListMetadata), &bytes_read, |
| FALSE) || |
| bytes_read != sizeof(PackedListMetadata)) { |
| return FileStatus::kMetadataReadFail; |
| } |
| |
| // Careful of versioning. For now, only support the latest version. |
| if (metadata.version != PackedListVersion::kCurrent) |
| return FileStatus::kInvalidFormatVersion; |
| |
| *array_size = metadata.module_count; |
| // Check for size 0. |
| if (!*array_size) |
| return FileStatus::kArraySizeZero; |
| |
| // Sanity check the array fits in a DWORD. |
| if (*array_size > |
| (std::numeric_limits<DWORD>::max() / sizeof(PackedListModule))) { |
| assert(false); |
| return FileStatus::kArrayTooBig; |
| } |
| |
| DWORD buffer_size = |
| static_cast<DWORD>(*array_size * sizeof(PackedListModule)); |
| *array_ptr = reinterpret_cast<PackedListModule*>(new uint8_t[buffer_size]); |
| |
| // Read in the array. |
| // NOTE: Ignore the rest of the file - other data could be stored at the end. |
| if (!::ReadFile(file, *array_ptr, buffer_size, &bytes_read, FALSE) || |
| bytes_read != buffer_size) { |
| delete[] * array_ptr; |
| *array_ptr = nullptr; |
| *array_size = 0; |
| return FileStatus::kArrayReadFail; |
| } |
| |
| // Ensure array is sorted (as expected). |
| if (!std::is_sorted(*array_ptr, *array_ptr + *array_size, |
| HashBinaryPredicate)) { |
| delete[] * array_ptr; |
| *array_ptr = nullptr; |
| *array_size = 0; |
| return FileStatus::kArrayNotSorted; |
| } |
| |
| return FileStatus::kSuccess; |
| } |
| |
| // Open a packed data file. |
| // |
| // - Returns kSuccess or kFileNotFound on success. |
| FileStatus OpenDataFile(HANDLE* file_handle) { |
| *file_handle = INVALID_HANDLE_VALUE; |
| std::wstring& file_path = GetBlFilePath(); |
| |
| // The path may have been overridden for testing. |
| if (file_path.empty()) { |
| if (!install_static::GetUserDataDirectory(&file_path, nullptr)) |
| return FileStatus::kUserDataDirFail; |
| file_path.append(kFileSubdir); |
| file_path.append(kBlFileName); |
| } |
| |
| // See if file exists. INVALID_HANDLE_VALUE alert! |
| *file_handle = |
| ::CreateFileW(file_path.c_str(), FILE_READ_DATA, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); |
| if (*file_handle == INVALID_HANDLE_VALUE) { |
| switch (::GetLastError()) { |
| case ERROR_FILE_NOT_FOUND: |
| case ERROR_PATH_NOT_FOUND: |
| return FileStatus::kFileNotFound; |
| case ERROR_ACCESS_DENIED: |
| return FileStatus::kFileAccessDenied; |
| default: |
| return FileStatus::kFileUnexpectedFailure; |
| } |
| } |
| |
| return FileStatus::kSuccess; |
| } |
| |
| // Example file location, relative to user data dir. |
| // %localappdata% / Google / Chrome SxS / User Data / ThirdPartyModuleList64 / |
| // |
| // - NOTE: kFileNotFound and kArraySizeZero are treated as kSuccess. |
| FileStatus InitInternal() { |
| // blacklist |
| // --------- |
| HANDLE handle = INVALID_HANDLE_VALUE; |
| FileStatus status = OpenDataFile(&handle); |
| if (status == FileStatus::kFileNotFound) |
| return FileStatus::kSuccess; |
| if (status == FileStatus::kSuccess) { |
| status = ReadInArray(handle, &g_bl_module_array_size, &g_bl_module_array); |
| ::CloseHandle(handle); |
| } |
| |
| if (status == FileStatus::kArraySizeZero) |
| return FileStatus::kSuccess; |
| return status; |
| } |
| |
| } // namespace |
| |
| //------------------------------------------------------------------------------ |
| // Public defines & functions |
| //------------------------------------------------------------------------------ |
| |
| bool IsModuleListed(const std::string& basename, |
| DWORD image_size, |
| DWORD time_date_stamp) { |
| assert(g_initialized); |
| if (!g_bl_module_array_size) |
| return false; |
| |
| // Max hex 32-bit value is 8 characters long. 2*8+1. |
| char buffer[17] = {}; |
| ::snprintf(buffer, sizeof(buffer), "%08lX%lx", time_date_stamp, image_size); |
| std::string code_id(buffer); |
| code_id = elf_sha1::SHA1HashString(code_id); |
| std::string basename_hash = elf_sha1::SHA1HashString(basename); |
| PackedListModule target = {}; |
| ::memcpy(target.basename_hash, basename_hash.data(), elf_sha1::kSHA1Length); |
| ::memcpy(target.code_id_hash, code_id.data(), elf_sha1::kSHA1Length); |
| |
| // Binary search for primary hash (basename). There can be more than one |
| // match. |
| auto pair = std::equal_range(g_bl_module_array, |
| g_bl_module_array + g_bl_module_array_size, |
| target, HashBinaryPredicate); |
| |
| // Search for secondary hash. |
| for (PackedListModule* i = pair.first; i != pair.second; ++i) { |
| if (!elf_sha1::CompareHashes(target.code_id_hash, i->code_id_hash)) |
| return true; |
| } |
| |
| // No match. |
| return false; |
| } |
| |
| std::wstring GetBlFilePathUsed() { |
| return GetBlFilePath(); |
| } |
| |
| // Grab the latest module list(s) from file. |
| FileStatus InitFromFile() { |
| // Debug check: InitFromFile should not be called more than once. |
| assert(!g_initialized); |
| |
| // TODO(pennymac): Possible UMA log for unexpected failures. |
| FileStatus status = InitInternal(); |
| |
| if (status == FileStatus::kSuccess) |
| g_initialized = true; |
| |
| return status; |
| } |
| |
| void OverrideFilePathForTesting(const std::wstring& new_bl_path) { |
| GetBlFilePath().assign(new_bl_path); |
| } |
| |
| void DeinitFromFileForTesting() { |
| if (!g_initialized) |
| return; |
| |
| delete[] g_bl_module_array; |
| g_bl_module_array = nullptr; |
| g_bl_module_array_size = 0; |
| GetBlFilePath().clear(); |
| |
| g_initialized = false; |
| } |
| |
| } // namespace third_party_dlls |