forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[chrome_elf whitelist] Add whitelist file initial support.
Not called yet. Plumbs in initialization from a packed whitelist file supplied out of band, and the exported IsModuleWhitelisted functionality. Also includes tests. BUG=769590 TEST=chrome_elf_unittests.exe, WhitelistFileTest.* Cq-Include-Trybots: master.tryserver.chromium.win:win10_chromium_x64_rel_ng Change-Id: Ib93b5af525b8bcc228664188acc83106262da900 Reviewed-on: https://chromium-review.googlesource.com/734204 Commit-Queue: Penny MacNeil <pennymac@chromium.org> Reviewed-by: Greg Thompson <grt@chromium.org> Cr-Commit-Position: refs/heads/master@{#516918}
- Loading branch information
Penny MacNeil
authored and
Commit Bot
committed
Nov 16, 2017
1 parent
211f3b4
commit f457147
Showing
7 changed files
with
504 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// 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_file_format.h" | ||
|
||
namespace whitelist { | ||
namespace { | ||
|
||
// TODO(pennymac): update subdir and filename with final shared defines. | ||
constexpr wchar_t kFileSubdir[] = | ||
L"\\ThirdPartyModuleList" | ||
#if defined(_WIN64) | ||
L"64"; | ||
#else | ||
L"32"; | ||
#endif | ||
constexpr wchar_t kFileName[] = L"\\dbfile"; | ||
|
||
// No concern about concurrency control in chrome_elf. | ||
bool g_initialized = false; | ||
|
||
// This will hold a packed whitelist module array, read directly from a | ||
// component-update file during InitComponent(). | ||
PackedWhitelistModule* g_module_array = nullptr; | ||
size_t g_module_array_size = 0; | ||
|
||
// NOTE: this "global" is only initialized once during InitComponent(). | ||
// NOTE: this is wrapped in a function to prevent exit-time dtors. | ||
std::wstring& GetFilePath() { | ||
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 whitelist. | ||
// | ||
// - Returns kSuccess or kArraySizeZero on success. | ||
FileStatus ReadInArray(HANDLE file) { | ||
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; | ||
|
||
g_module_array_size = metadata.module_count; | ||
// Check for size 0. | ||
if (!g_module_array_size) | ||
return FileStatus::kArraySizeZero; | ||
|
||
// Sanity check the array fits in a DWORD. | ||
if (g_module_array_size > | ||
(std::numeric_limits<DWORD>::max() / sizeof(PackedWhitelistModule))) { | ||
assert(false); | ||
return FileStatus::kArrayTooBig; | ||
} | ||
|
||
DWORD buffer_size = | ||
static_cast<DWORD>(g_module_array_size * sizeof(PackedWhitelistModule)); | ||
g_module_array = | ||
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, g_module_array, buffer_size, &bytes_read, FALSE) || | ||
bytes_read != buffer_size) { | ||
delete[] g_module_array; | ||
g_module_array = nullptr; | ||
g_module_array_size = 0; | ||
return FileStatus::kArrayReadFail; | ||
} | ||
// TODO(pennymac): calculate cost of is_sorted() call against real database | ||
// array. Is it too expensive for startup? Maybe do a delayed check? | ||
if (!std::is_sorted(g_module_array, g_module_array + g_module_array_size, | ||
HashBinaryPredicate)) { | ||
delete[] g_module_array; | ||
g_module_array = nullptr; | ||
g_module_array_size = 0; | ||
return FileStatus::kArrayNotSorted; | ||
} | ||
|
||
return FileStatus::kSuccess; | ||
} | ||
|
||
// Example file location, relative to user data dir. | ||
// %localappdata% / Google / Chrome SxS / User Data / ThirdPartyModuleList64 / | ||
// | ||
// TODO(pennymac): Missing or malformed component file shouldn't happen. | ||
// Possible UMA log in future. | ||
FileStatus InitInternal() { | ||
std::wstring& file_path = GetFilePath(); | ||
// 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(kFileName); | ||
} | ||
|
||
// See if file exists. INVALID_HANDLE_VALUE alert! | ||
HANDLE file = | ||
::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 == INVALID_HANDLE_VALUE) | ||
return FileStatus::kFileNotFound; | ||
|
||
FileStatus status = ReadInArray(file); | ||
::CloseHandle(file); | ||
|
||
return status; | ||
} | ||
|
||
} // namespace | ||
|
||
//------------------------------------------------------------------------------ | ||
// Public defines & functions | ||
//------------------------------------------------------------------------------ | ||
|
||
bool IsModuleWhitelisted(const std::string& basename, | ||
DWORD image_size, | ||
DWORD time_date_stamp) { | ||
assert(g_initialized); | ||
if (!g_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_module_array, g_module_array + g_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 GetFilePathUsed() { | ||
return GetFilePath(); | ||
} | ||
|
||
// Grab the latest whitelist file. | ||
FileStatus InitFromFile() { | ||
// Debug check: InitFromFile should not be called more than once. | ||
assert(!g_initialized); | ||
|
||
// TODO(pennymac): Should kArraySizeZero be a failure? Or just UMA? | ||
FileStatus status = InitInternal(); | ||
|
||
if (status == FileStatus::kSuccess) | ||
g_initialized = true; | ||
|
||
return status; | ||
} | ||
|
||
void OverrideFilePathForTesting(const std::wstring& new_path) { | ||
GetFilePath().assign(new_path); | ||
} | ||
|
||
} // namespace whitelist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// 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. | ||
|
||
#ifndef CHROME_ELF_WHITELIST_WHITELIST_FILE_H_ | ||
#define CHROME_ELF_WHITELIST_WHITELIST_FILE_H_ | ||
|
||
#include "windows.h" | ||
|
||
#include <string> | ||
|
||
namespace whitelist { | ||
|
||
// "static_cast<int>(FileStatus::value)" to access underlying value. | ||
enum class FileStatus { | ||
kSuccess = 0, | ||
kUserDataDirFail = 1, | ||
kFileNotFound = 2, | ||
kMetadataReadFail = 3, | ||
kInvalidFormatVersion = 4, | ||
kArraySizeZero = 5, | ||
kArrayTooBig = 6, | ||
kArrayReadFail = 7, | ||
kArrayNotSorted = 8, | ||
COUNT | ||
}; | ||
|
||
// Look up a binary based on the required data points. | ||
// - Returns true if match found in whitelist. I.E. Allow module load. | ||
bool IsModuleWhitelisted(const std::string& basename, | ||
DWORD image_size, | ||
DWORD time_date_stamp); | ||
|
||
// Get the full path of the whitelist file used. | ||
std::wstring GetFilePathUsed(); | ||
|
||
// Initialize internal whitelist from file. | ||
FileStatus InitFromFile(); | ||
|
||
// Sets the path to the whitelist file for use by tests. | ||
void OverrideFilePathForTesting(const std::wstring& new_path); | ||
|
||
} // namespace whitelist | ||
|
||
#endif // CHROME_ELF_WHITELIST_WHITELIST_FILE_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// 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. | ||
|
||
// ----------------------------------------------------------------------------- | ||
// This defines the expected contents of a packed whitelist file. | ||
// - At offset 0 of the file: {PackedWhitelistMetadata} | ||
// - Immediately following: {Array of PackedWhitelistModule} | ||
// - Anything else can be stored in the rest of the file. | ||
// | ||
// - It's a requirement that the file be packed little-endian and also that | ||
// 32-bit alignment == 64-bit alignment (so no handling required). | ||
// - It's also required that the array be *sorted*. First by basename hash, | ||
// second by code_id hash (there can be multiple of the same basename hash). | ||
// ----------------------------------------------------------------------------- | ||
|
||
#ifndef CHROME_ELF_WHITELIST_WHITELIST_FILE_FORMAT_H_ | ||
#define CHROME_ELF_WHITELIST_WHITELIST_FILE_FORMAT_H_ | ||
|
||
namespace whitelist { | ||
|
||
enum PackedWhitelistVersion : uint32_t { | ||
kInitialVersion = 1, | ||
kCurrent = kInitialVersion, | ||
}; | ||
|
||
struct PackedWhitelistMetadata { | ||
// The version of the packed whitelist format. This should always be ordered | ||
// first for backward compatibility purposes. | ||
PackedWhitelistVersion version; | ||
// The number of PackedWhitelistModule elements that follows the metadata in | ||
// the packed whitelist file. | ||
uint32_t module_count; | ||
}; | ||
|
||
struct PackedWhitelistModule { | ||
// SHA1 of lowercase basename (no path). | ||
uint8_t basename_hash[20]; | ||
// Code ID. This is equivalent to the string generated by formatting | ||
// the FileHeader.TimeDateStamp and OptionalHeader.SizeOfImage with the | ||
// formatting string %08X%x. Then SHA1 the string. | ||
uint8_t code_id_hash[20]; | ||
}; | ||
|
||
// These struct are directly written to a file. Therefore the padding should | ||
// be consistent across compilations. | ||
static_assert(sizeof(PackedWhitelistMetadata) == 8, | ||
"The actual padding of the PackedWhitelistMetadata struct " | ||
"doesn't match the expected padding"); | ||
static_assert(sizeof(PackedWhitelistModule) == 40, | ||
"The actual padding of the PackedWhitelistModule struct doesn't " | ||
"match the expected padding"); | ||
|
||
} // namespace whitelist | ||
|
||
#endif // CHROME_ELF_WHITELIST_WHITELIST_FILE_FORMAT_H_ |
Oops, something went wrong.