Skip to content

Commit

Permalink
[chrome_elf whitelist] Add whitelist file initial support.
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 7 changed files with 504 additions and 5 deletions.
6 changes: 6 additions & 0 deletions chrome_elf/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,16 @@ static_library("whitelist") {
sources = [
"whitelist/whitelist.cc",
"whitelist/whitelist.h",
"whitelist/whitelist_file.cc",
"whitelist/whitelist_file.h",
"whitelist/whitelist_file_format.h",
"whitelist/whitelist_ime.cc",
"whitelist/whitelist_ime.h",
]
deps = [
":nt_registry",
":sha1",
"//chrome/install_static:install_static_util",
]
}

Expand All @@ -251,6 +256,7 @@ test("chrome_elf_unittests") {
"nt_registry/nt_registry_unittest.cc",
"run_all_unittests.cc",
"sha1/sha1_unittest.cc",
"whitelist/whitelist_file_unittest.cc",
"whitelist/whitelist_ime_unittest.cc",
]
include_dirs = [ "$target_gen_dir" ]
Expand Down
4 changes: 3 additions & 1 deletion chrome_elf/whitelist/DEPS
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
include_rules = [
# Nothing from base.
"-base",
# Nothing from chrome.
# Nothing from chrome except install_static.
"-chrome",
"+chrome/install_static",
# Carefully control local includes.
"-chrome_elf",
"+chrome_elf/nt_registry/nt_registry.h",
"+chrome_elf/sha1/sha1.h",
"+chrome_elf/whitelist",
]
specific_include_rules = {
Expand Down
220 changes: 220 additions & 0 deletions chrome_elf/whitelist/whitelist_file.cc
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
45 changes: 45 additions & 0 deletions chrome_elf/whitelist/whitelist_file.h
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_
56 changes: 56 additions & 0 deletions chrome_elf/whitelist/whitelist_file_format.h
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_
Loading

0 comments on commit f457147

Please sign in to comment.