blob: 7359f9225489e13e88c308ad65e4471958f8888b [file] [log] [blame]
// 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/whitelist/whitelist_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/whitelist/whitelist_packed_format.h"
namespace whitelist {
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 InitComponent().
PackedWhitelistModule* 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
//------------------------------------------------------------------------------
// Returns -1 if |first| < |second|
// Returns 0 if |first| == |second|
// Returns 1 if |first| > |second|
int CompareHashes(const uint8_t* first, const uint8_t* second) {
// Compare bytes, high-order to low-order.
for (size_t i = 0; i < elf_sha1::kSHA1Length; ++i) {
if (first[i] < second[i])
return -1;
if (first[i] > second[i])
return 1;
// else they are equal, continue;
}
return 0;
}
// Binary predicate compare function for use with
// std::equal_range/std::is_sorted. Must return TRUE if lhs < rhs.
bool HashBinaryPredicate(const PackedWhitelistModule& lhs,
const PackedWhitelistModule& rhs) {
return 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,
PackedWhitelistModule** array_ptr) {
PackedWhitelistMetadata metadata;
DWORD bytes_read = 0;
if (!::ReadFile(file, &metadata, sizeof(PackedWhitelistMetadata), &bytes_read,
FALSE) ||
bytes_read != sizeof(PackedWhitelistMetadata)) {
return FileStatus::kMetadataReadFail;
}
// Careful of versioning. For now, only support the latest version.
if (metadata.version != PackedWhitelistVersion::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(PackedWhitelistModule))) {
assert(false);
return FileStatus::kArrayTooBig;
}
DWORD buffer_size =
static_cast<DWORD>(*array_size * sizeof(PackedWhitelistModule));
*array_ptr =
reinterpret_cast<PackedWhitelistModule*>(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);
PackedWhitelistModule 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 (PackedWhitelistModule* i = pair.first; i != pair.second; ++i) {
if (!CompareHashes(target.code_id_hash, i->code_id_hash))
return true;
}
// No match.
return false;
}
std::wstring GetBlFilePathUsed() {
return GetBlFilePath();
}
// Grab the latest whitelist and blacklist.
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);
}
} // namespace whitelist